Java version (#5)

* Create github-actions-demoyml

* this is for action test

* Create github-actions-demo.yml

* Create rocketmqTest.yml

* Update entry.sh

* Update entry.sh

* Update entry.sh

* Update entry.sh

* Update entry.sh

* Update entry.sh

* Delete .github/workflows directory

* Update entry.sh

* Delete rocketmqTest.yml

* Delete testAction.txt

* feat: add java module

* fix: dockerfile

* fix: add wait time

* fix: find bug

* fix: delete timeout

* feat: add test

* fix: add default values

* fix: revise README.md

* fix: add projectName

* fix: revise README.md

* fix: revise README.md

* fix: revise README.md
diff --git a/.asf.yaml b/.asf.yaml
deleted file mode 100644
index 3e3fa58..0000000
--- a/.asf.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-
-github:
-  description: "Apache RocketMQ Test Tool"
-  homepage: https://rocketmq.apache.org/
-notifications:
-  commits:      commits@rocketmq.apache.org
-  issues:       commits@rocketmq.apache.org
-  pullrequests: commits@rocketmq.apache.org
-  jobs:         commits@rocketmq.apache.org
-  discussions:  dev@rocketmq.apache.org
diff --git a/.gitignore b/.gitignore
index d7fba17..9c17fa2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,15 +1,40 @@
-*dependency-reduced-pom.xml
-.classpath
-.project
-.settings/
 target/
-devenv
-*.log*
+result*.md
+test_report*
+nacos_nodejs_testlog.txt
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
 *.iml
-.idea/
-*.versionsBackup
-!NOTICE-BIN
-!LICENSE-BIN
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+nacos*
+testlog.txt
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
 .DS_Store
-localbin
-nohup.out
diff --git a/Dockerfile b/Dockerfile
index cacc4a6..8f7323b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,10 +15,18 @@
 # limitations under the License.
 #
 # Container image that runs your code
-FROM cloudnativeofalibabacloud/test-runner:v0.0.1
+FROM maven:3.8.5-jdk-8-slim
 
-# Copies your code file from your action repository to the filesystem path `/` of the container
-COPY entry.sh /entry.sh
+MAINTAINER wuyfee "wyf_mohen@163.com"
 
-# Code file to execute when the docker container starts up (`entry.sh`)
-ENTRYPOINT ["/entry.sh"]
+EXPOSE  9082
+COPY src /src
+COPY pom.xml /pom.xml
+ENV KUBECONFIG=/root/.kube/config
+
+
+RUN mvn clean install -Dmaven.test.skip=true \
+    && mv /target/rocketmq-test-tool-1.0-SNAPSHOT-jar-*.jar ./rocketmq-test-tool.jar \
+    && rm -rf /pom.xml /src /target
+
+ENTRYPOINT ["/bin/sh", "-c","java -jar /rocketmq-test-tool.jar -yamlString=\"${0}\" "]
diff --git a/README.md b/README.md
index bdc4a18..bd3803d 100644
--- a/README.md
+++ b/README.md
@@ -1,60 +1,268 @@
 # Apache RocketMQ Test Tool
-
-This tool uses Helm and KubeVela to deploy applications and execute E2E tests in Kubernetes.
+This tool uses Helm and KubeVela to deploy applications and execute tests in Kubernetes.
 KubeVela needs to be installed in Kubernetes before use.
 
-# Usage
+
+## Preparation
+- Install kubevela in Kubernetes.
+  - An account in vela system. 
+    - If you have an account, you should set this velauxUsername and velauxPassword in yamlString. 
+    - If velauxUsername and velauxPassword is not included in yamlString, you should create an account in vela system, password and username should be created by following function, this tool will genarate username and password by ask config: 
+```java
+    /**
+      * get velaUX username and password
+      *
+      * @param kubeConfig ask config
+      * @return username:password
+      */
+    public String getAuthInfoFromConfig(String kubeConfig) {
+          String text = kubeConfig.length() > 150 ? kubeConfig.substring(kubeConfig.length() - 150) : kubeConfig;
+          StringBuilder userName = new StringBuilder();
+          StringBuilder password = new StringBuilder();
+          boolean digitMark = false;
+          for (int index = text.length() - 1; index >= 0; index--) {
+              if (userName.length() >= 6 && password.length() >= 12) {
+                  break;
+              }
+              boolean isLetter = Character.isLetter(text.charAt(index));
+              boolean isDigit = Character.isDigit(text.charAt(index));
+              if (isDigit || isLetter) {
+                  if (isLetter && userName.length() < 6) {
+                      userName.append(Character.toLowerCase(text.charAt(index)));
+                  }
+                  if (password.length() < 12) {
+                      if (digitMark && isDigit) {
+                          password.append(text.charAt(index));
+                          digitMark = false;
+                      } else if (!digitMark && isLetter) {
+                          password.append(text.charAt(index));
+                          digitMark = true;
+                      }
+                  }
+              }
+          }
+          return userName + ":" + password;
+      }
+``` 
+#### example
+you should input a yaml format string.
+Attention:
+- AskConfig must be encoder by base64.
+- If some of the parameters are not included, set it to null.
+- You can add or delete params in "helm" and "ENV" segment .
+###### deploy
+use kubevela API to deploy application.
+```yaml
+yamlString: 
+  action: deploy
+  namespace: rocketmq-457628-0
+  askConfig: ***********
+  velauxUsername: ***
+  velauxPassword: ***
+  projectName: wyftest
+  waitTimes: 1200
+  velaAppDescription: rocketmq-push-ci-123456@abcdefg
+  repoName: rocketmq
+  helm:
+    chart: ./rocketmq-k8s-helm
+    git:
+      branch: master
+    repoType: git
+    retries: 3
+    url: https://ghproxy.com/https://github.com/apache/rocketmq-docker.git
+    values:
+      nameserver:
+        image:
+          repository: wuyfeedocker/rocketm-ci
+          tag: develop-3b416669-cab7-41b4-8cc8-4af851944de2-ubuntu
+      broker:
+        image:
+          repository: wuyfeedocker/rocketm-ci
+          tag: develop-3b416669-cab7-41b4-8cc8-4af851944de2-ubuntu
+      proxy:
+        image:
+          repository: wuyfeedocker/rocketm-ci
+          tag: develop-3b416669-cab7-41b4-8cc8-4af851944de2-ubuntu
+```
+| option             | description                                 | default | necessary |
+|--------------------|---------------------------------------------|--------|-----------|
+| action             | deploy                                      | null   | yes       |
+|velauxUsername      | vela username                               | null   | no        |
+| velauxPassword | vela password                               | null   | no        |
+|projectName | vela project | wyftest | no |
+| namespace          | pod namespace                               | null   | yes       |
+| askConfig          | ask config                                  | null   | yes       |
+| waitTimes          | deploy max time (second)                    | 900 | no        |
+| velaAppDescription | vela app description                        | ""     | no        |
+| repoName           | repo name(such as "nacos", "rocketmq" .etc) | null   | yes       |
+| helm         | helm chart                                  | null   | yes       |
+
+###### test
+use kubernetes API to execute test.
+```yaml
+yamlString: |
+  action: test
+  namespace: rocketmq-457628-0
+  askConfig: ***********
+  API_VERSION: v1
+  KIND: Pod
+  RESTART_POLICY: Never
+  ENV:
+    WAIT_TIME: 600 
+    REPO_NAME: apache/rocketmq-e2e
+    CODE: https://github.com/apache/rocketmq-e2e
+    BRANCH: master
+    CODE_PATH: java/e2e-v4
+    CMD: mvn -B test
+    ALL_IP: null
+  CONTAINER:
+    IMAGE: cloudnativeofalibabacloud/test-runner:v0.0.4
+    RESOURCE_LIMITS:
+      cpu: 8
+      memory: 8Gi
+    RESOURCE_REQUIRE:
+      cpu: 8
+      memory: 8Gi
+```
+| option                            | description                       | default    | necessary |
+|-----------------------------------|-----------------------------------|---------|-----------|
+| action                            | test                              | null       | yes       |
+| namespace                         | pod namespace                     | null       | yes       |
+| askConfig                         | ask config                        | null       | yes       |
+| API_VERSION                         | Kubernetes API version            | v1         | no        |
+| KIND                | pod kind                          | Pod       | no        |
+| RESTART_POLICY                          | pod restart policy                | Never       | no        |
+| ENV.WAIT_TIME                     | test pod expiration time (second) | 900      | no        |
+| ENV.REPO_NAME                     | repository whole name             | null       | yes       |
+| ENV.CODE                          | test code url                     | null       | yes       |
+| ENV.BRANCH                        | code branch                       | null       | yes       |
+| ENV.CODE_PATH                     | test code path                    | null       | yes       |
+| ENV.CMD                           | test command                      | null       | yes       |
+| ENV.ALL_IP                        | cluster ips                       | null       | no        |
+| CONTAINER.IMAGE                   | pod container image               | null       | yes       |
+| CONTAINER.RESOURCE_LIMITS.cpu     | pod container cpu limit           | null       | no        |
+| CONTAINER.RESOURCE_LIMITS.memory  | pod container memory limit        | null       | no        |
+| CONTAINER.RESOURCE_REQUIRE.cpu    | pod container cpu require         | null       | no        |
+| CONTAINER.RESOURCE_REQUIRE.memory | pod container memory require      | null       | no        |
+###### clean
+use kubernetes API and kubevela API to clean resource.
+```yaml
+yamlString: |
+  action: clean
+  namespace: rocketmq-457628-0
+  velauxUsername: ***
+  velauxPassword: ***
+  askConfig: ***********
+```
+| option         | description   | default    | necessary |
+|----------------|---------------|---------|-----------|
+| action         | clean         | null     | yes       |
+| velauxUsername | vela username |   null  | no |
+| velauxPassword | vela password | null       | no        |
+| namespace      | pod namespace | null       | yes       |
+| askConfig      | ask config               | null   | yes       |
+
+
+## Usage
 
 <!-- start usage -->
-## Use helm chart to deploy your app in Kubernetes
-```yaml
-  - uses: apache/rocketmq-test-tool@v1
-    name: Deploy
-    with:
-      action: "deploy"
-      ask-config: "${{ secrets.KUBE_CONFIG }}"
-      test-version: "v1.0"
-      chart-git: "https://github.com/your-helm-chart.git"
-      chart-branch: "main"
-      chart-path: "."
-      job-id: 1
-      helm-values: |
-        app:
-          image:
-            repository: ${{env.DOCKER_REPO}}
-            tag: v1.0
+## start project
+### by java jar
+```agsl
+cd test-tools
+mvn clean install -Dmaven.test.skip=true
+mv /target/rocketmq-test-tool-*-SNAPSHOT-jar-*.jar ./rocketmq-test-tool.jar
+# quick start run
+jar -jar rocketmq-test-tool.jar -yamlString=${your yamlString}
 ```
-## Execute your E2E test
-```yaml
-  - uses: apache/rocketmq-test-tool@v1
-    name: e2e test
-    with:
-      action: "test"
-      ask-config: "${{ secrets.KUBE_CONFIG }}"
-      test-version: "v1.0"
-      test-code-git: "https://github.com/your-e2e-test.git"
-      test-code-branch: "main"
-      test-code-path: ./
-      test-cmd: "your test command"
-      job-id: 1
-  - uses: actions/upload-artifact@v3
-    if: always()
-    name: Upload test log
-    with:
-      name: testlog.txt
-      path: testlog.txt
+### by docker images
 ```
-## Clean your app in Kubernetes
+# build docker images
+docker build -t test-tool
+# quick start run
+docker run -it test-tool -yamlString=${your yamlString}
+```
+### in github action
+#### deploy 
 ```yaml
-  - uses: apache/rocketmq-test-tool@v1
+- uses: apache/rocketmq-test-tool@java-dev
+  name: Deploy nacos
+  with:
+    yamlString: |
+      action: deploy
+      namespace: nacos-123456789-0
+      askConfig: ******
+      waitTimes: 2000
+      velaAppDescription: nacos-push-ci-123@$abcdefg
+      repoName: nacos
+      helm:
+        chart: ./cicd/helm
+        git:
+          branch: main
+        repoType: git
+        retries: 3
+        url: https://ghproxy.com/https://github.com/nacos-group/nacos-e2e.git
+        values:
+          namespace: nacos-123456789-0
+          global:
+            mode: cluster
+          nacos:
+            replicaCount: 3
+            image:
+              repository: wuyfeedocker/nacos-ci
+              tag: develop-cee62800-0cb5-478f-9e42-aeb1124db716-8
+            storage:
+              type: mysql
+              db:
+                port: 3306
+                username: nacos
+                password: nacos
+                param: characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false
+          service:
+            nodePort: 30000
+            type: ClusterIP
+```
+#### test
+```yaml
+steps:
+  - uses: apache/rocketmq-test-tool@java-dev
+    name: java e2e test
+    with:
+      yamlString: |
+        action: test
+        namespace: nacos-123456789-0
+        askConfig: ******
+        API_VERSION: v1
+        KIND: Pod
+        RESTART_POLICY: Never
+        ENV:
+          WAIT_TIME: 900
+          REPO_NAME: nacos-group/nacos-e2e
+          CODE: https://github.com/nacos-group/nacos-e2e
+          BRANCH: main
+          CODE_PATH: java/nacos-2X
+          CMD: mvn clean test -B
+          ALL_IP: null
+        CONTAINER:
+          IMAGE: cloudnativeofalibabacloud/test-runner:v0.0.4
+          RESOURCE_LIMITS:
+            cpu: 8
+            memory: 8Gi
+          RESOURCE_REQUIRE:
+            cpu: 8
+            memory: 8Gi
+```
+clean
+```yaml
+steps:
+  - uses: apache/rocketmq-test-tool@java-dev
     name: clean
     with:
-      action: "clean"
-      ask-config: "${{ secrets.KUBE_CONFIG }}"
-      test-version: "v1.0"
-      job-id: 1
+      yamlString: |
+        action: clean
+        namespace: nacos-123456789-0
+        askConfig: ******
 ```
 <!-- end usage -->
-
 # License
 [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) Copyright (C) Apache Software Foundation
diff --git a/action.yml b/action.yml
index 461e9ff..0aa3b73 100644
--- a/action.yml
+++ b/action.yml
@@ -18,77 +18,12 @@
 name: 'Cloud Native CI Action'
 description: ''
 inputs:
-  action: # id of input
-    description: 'action'
+  yamlString: # yalm string
+    description: 'all params'
     required: true
-    default: ''
-  test-version: # id of input
-    description: 'test version'
-    required: true
-    default: ''
-  ask-config:  # id of input
-    description: 'ask config'
-    required: true
-    default: ''
-  docker-repo-username: # id of input
-    description: 'docker repo username'
-    required: false
-    default: ''
-  docker-repo-password: # id of input
-    description: 'docker repo password'
-    required: false
-    default: ''
-  chart-git: # id of input
-    description: 'chart git'
-    required: false
-    default: ''
-  chart-branch: # id of input
-    description: 'chart branch'
-    required: false
-    default: ''
-  chart-path: # id of input
-    description: 'chart path'
-    required: false
-    default: './'
-  test-code-git: # id of input
-    description: 'test code git'
-    required: false
-    default: ''
-  test-code-branch: # id of input
-    description: 'test code branch'
-    required: false
-    default: ''
-  test-code-path: # id of input
-    description: 'test code path'
-    required: false
-    default: ''
-  test-cmd: # id of input
-    description: 'test cmd'
-    required: false
-    default: 'mvn -B test'
-  job-id: # id of input
-    description: 'job id'
-    required: true
-    default: ''
-  helm-values: # id of input
-    description: 'helm values'
-    required: true
-    default: ''
+    default: ""
 runs:
   using: 'docker'
   image: 'Dockerfile'
   args:
-    - ${{ inputs.action }}
-    - ${{ inputs.test-version }}
-    - ${{ inputs.ask-config }}
-    - ${{ inputs.docker-repo-username }}
-    - ${{ inputs.docker-repo-password }}
-    - ${{ inputs.chart-git }}
-    - ${{ inputs.chart-branch }}
-    - ${{ inputs.chart-path }}
-    - ${{ inputs.test-code-git }}
-    - ${{ inputs.test-code-branch }}
-    - ${{ inputs.test-code-path }}
-    - ${{ inputs.test-cmd }}
-    - ${{ inputs.job-id }}
-    - ${{ inputs.helm-values }}
+    - ${{ inputs.yamlString }}
diff --git a/entry.sh b/entry.sh
deleted file mode 100755
index 0e814f4..0000000
--- a/entry.sh
+++ /dev/null
@@ -1,327 +0,0 @@
-#!/bin/sh -l
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-#
-
-ACTION=$1
-VERSION=$2
-ASK_CONFIG=$3
-DOCKER_REPO_USERNAME=$4
-DOCKER_REPO_PASSWORD=$5
-CHART_GIT=$6
-CHART_BRANCH=$7
-CHART_PATH=$8
-TEST_CODE_GIT=${9}
-TEST_CODE_BRANCH=${10}
-TEST_CODE_PATH=${11}
-TEST_CMD_BASE=${12}
-JOB_INDEX=${13}
-HELM_VALUES=${14}
-
-export VERSION
-export CHART_GIT
-export CHART_BRANCH
-export CHART_PATH
-export REPO_NAME=`echo ${GITHUB_REPOSITORY#*/} | sed -e "s/\//-/g" | cut -c1-36 | tr '[A-Z]' '[a-z]'`
-export WORKFLOW_NAME=${GITHUB_WORKFLOW}
-export RUN_ID=${GITHUB_RUN_ID}
-export TEST_CODE_GIT
-export TEST_CODE_BRANCH
-export TEST_CODE_PATH
-export YAML_VALUES=`echo "${HELM_VALUES}" | sed -s 's/^/          /g'`
-
-echo "Start test version: ${GITHUB_REPOSITORY}@${VERSION}"
-
-echo "************************************"
-echo "*          Set config...           *"
-echo "************************************"
-mkdir -p ${HOME}/.kube
-kube_config=$(echo "${ASK_CONFIG}" | base64 -d)
-echo "${kube_config}" > ${HOME}/.kube/config
-export KUBECONFIG="${HOME}/.kube/config"
-
-VELA_APP_TEMPLATE='
-apiVersion: core.oam.dev/v1beta1
-kind: Application
-metadata:
-  name: ${VELA_APP_NAME}
-  description: ${REPO_NAME}-${WORKFLOW_NAME}-${RUN_ID}@${VERSION}
-spec:
-  components:
-    - name: ${REPO_NAME}
-      type: helm
-      properties:
-        chart: ${CHART_PATH}
-        git:
-          branch: ${CHART_BRANCH}
-        repoType: git
-        retries: 3
-        secretRef: \047\047
-        url: ${CHART_GIT}
-        values:
-${YAML_VALUES}'
-
-echo -e "${VELA_APP_TEMPLATE}" > ./velaapp.yaml
-sed -i '1d' ./velaapp.yaml
-
-env_uuid=${REPO_NAME}-${GITHUB_RUN_ID}-${JOB_INDEX}
-
-
-if [ ${ACTION} == "deploy" ]; then
-  echo "************************************"
-  echo "*     Create env and deploy...     *"
-  echo "************************************"
-
-  echo ${VERSION}: ${env_uuid} deploy start
-
-  vela env init ${env_uuid} --namespace ${env_uuid}
-
-  export VELA_APP_NAME=${env_uuid}
-  envsubst < ./velaapp.yaml > velaapp-${REPO_NAME}.yaml
-  cat velaapp-${REPO_NAME}.yaml
-
-  vela env set ${env_uuid}
-  vela up -f "velaapp-${REPO_NAME}.yaml"
-
-  app=${env_uuid}
-
-  status=`vela status ${app} -n ${app}`
-  echo $status
-  res=`echo $status | grep "Create helm release successfully"`
-  let count=0
-  while [ -z "$res" ]
-  do
-      if [ $count -gt 240 ]; then
-        echo "env ${app} deploy timeout..."
-        exit 1
-      fi
-      echo "waiting for env ${app} ready..."
-      sleep 5
-      status=`vela status ${app} -n ${app}`
-      stopped=`echo $status | grep "not found"`
-      if [ ! -z "$stopped" ]; then
-          echo "env ${app} deploy stopped..."
-          exit 1
-      fi
-      res=`echo $status | grep "Create helm release successfully"`
-      let count=${count}+1
-  done
-fi
-
-TEST_POD_TEMPLATE='
-apiVersion: v1
-kind: Pod
-metadata:
-  name: ${test_pod_name}
-  namespace: ${ns}
-spec:
-  restartPolicy: Never
-  containers:
-  - name: ${test_pod_name}
-    image: cloudnativeofalibabacloud/test-runner:v0.0.3
-    resources:
-          limits:
-            cpu: "8"
-            memory: "8Gi"
-          requests:
-            cpu: "8"
-            memory: "8Gi"
-    env:
-    - name: CODE
-      value: ${TEST_CODE_GIT}
-    - name: BRANCH
-      value: ${TEST_CODE_BRANCH}
-    - name: CODE_PATH
-      value: ${TEST_CODE_PATH}
-    - name: ALL_IP
-      value: ${ALL_IP}
-    - name: CMD
-      value: |
-${TEST_CMD}
-'
-
-echo -e "${TEST_POD_TEMPLATE}" > ./testpod.yaml
-sed -i '1d' ./testpod.yaml
-
-if [ ${ACTION} == "test" ]; then
-  echo "************************************"
-  echo "*            E2E Test...           *"
-  echo "************************************"
-
-  ns=${env_uuid}
-  test_pod_name=test-${env_uuid}-${RANDOM}
-  export test_pod_name
-
-  echo namespace: $ns
-  all_pod_name=`kubectl get pods --no-headers -o custom-columns=":metadata.name" -n ${ns}`
-  ALL_IP=""
-  for pod in $all_pod_name;
-  do
-      if [ ! -z `echo "${pod}" | grep "test-${env_uuid}"` ]; then
-        continue
-      fi
-      POD_HOST=$(kubectl get pod ${pod} --template={{.status.podIP}} -n ${ns})
-      ALL_IP=${pod}:${POD_HOST},${ALL_IP}
-  done
-
-  echo $ALL_IP
-  echo $TEST_CODE_GIT
-  echo $TEST_CMD_BASE
-
-  export ALL_IP
-  export ns
-
-  TEST_CMD=`echo "${TEST_CMD_BASE}" | sed -s 's/^/        /g'`
-
-  echo $TEST_CMD
-  export TEST_CMD
-
-  envsubst < ./testpod.yaml > ./testpod-${ns}.yaml
-  cat ./testpod-${ns}.yaml
-
-  kubectl apply -f ./testpod-${ns}.yaml
-
-  sleep 5
-
-  pod_status=`kubectl get pod ${test_pod_name} --template={{.status.phase}} -n ${ns}`
-  if [ -z "$pod_status" ]; then
-      pod_status="Pending"
-  fi
-
-  while [ "${pod_status}" == "Pending" ] || [ "${pod_status}" == "Running" ]
-  do
-      echo waiting for ${test_pod_name} test done...
-      sleep 5
-      pod_status=`kubectl get pod ${test_pod_name} --template={{.status.phase}} -n ${ns}`
-      if [ -z "$pod_status" ]; then
-          pod_status="Pending"
-      fi
-      test_done=`kubectl exec -i ${test_pod_name} -n ${ns} -- ls /root | grep testdone`
-      if [ ! -z "$test_done" ]; then
-        echo "Test status: test done"
-          if [ ! -d "./test_report" ]; then
-            echo "Copy test reports"
-            kubectl cp --retries=10 ${test_pod_name}:/root/testlog.txt testlog.txt -n ${ns}
-            mkdir -p test_report
-            cd test_report
-            kubectl cp --retries=10 ${test_pod_name}:/root/code/${TEST_CODE_PATH}/target/surefire-reports/. . -n ${ns}
-            rm -rf *.txt
-            ls
-            cd -
-          fi
-      fi
-  done
-
-  exit_code=`kubectl get pod ${test_pod_name} --output="jsonpath={.status.containerStatuses[].state.terminated.exitCode}" -n ${ns}`
-  kubectl delete pod ${test_pod_name} -n ${ns}
-  echo E2E Test exit code: ${exit_code}
-  exit ${exit_code}
-fi
-
-if [ ${ACTION} == "test_local" ]; then
-  echo "************************************"
-  echo "*        E2E Test local...         *"
-  echo "************************************"
-
-  wget https://dlcdn.apache.org/maven/maven-3/3.8.7/binaries/apache-maven-3.8.7-bin.tar.gz
-  tar -zxvf apache-maven-3.8.7-bin.tar.gz -C /opt/
-  export PATH=$PATH:/opt/apache-maven-3.8.7/bin
-
-  ns=${env_uuid}
-
-  echo namespace: $ns
-  all_pod_name=`kubectl get pods --no-headers -o custom-columns=":metadata.name" -n ${ns}`
-  ALL_IP=""
-  for pod in $all_pod_name;
-  do
-      label=`kubectl get pod ${pod} --output="jsonpath={.metadata.labels.app\.kubernetes\.io/name}" -n ${ns}`
-      pod_port=`kubectl get -o json services --selector="app.kubernetes.io/name=${label}" -n ${ns} | jq -r '.items[].spec.ports[].port'`
-      echo "${pod}: ${pod_port}"
-      for port in ${pod_port};
-      do
-          kubectl port-forward ${pod} ${port}:${port} -n ${ns} &
-          res=$?
-          if [ ${res} -ne 0 ]; then
-            echo "kubectl port-forward error: ${pod} ${port}:${port}"
-            exit ${res}
-          fi
-      done
-      ALL_IP=${pod}:"127.0.0.1",${ALL_IP}
-      sleep 3
-  done
-
-  echo $ALL_IP
-  echo $TEST_CODE_GIT
-  echo $TEST_CMD_BASE
-
-  export ALL_IP
-  export ns
-  is_mvn_cmd=`echo $TEST_CMD_BASE | grep "mvn"`
-  if [ ! -z "$is_mvn_cmd" ]; then
-      TEST_CMD="$TEST_CMD_BASE -DALL_IP=${ALL_IP}"
-  else
-      TEST_CMD=$TEST_CMD_BASE
-  fi
-  echo $TEST_CMD
-
-  git clone $TEST_CODE_GIT -b $TEST_CODE_BRANCH code
-
-  cd code
-  cd $TEST_CODE_PATH
-  ${TEST_CMD}
-  exit_code=$?
-
-  killall kubectl
-  exit ${exit_code}
-fi
-
-if [ ${ACTION} == "clean" ]; then
-    echo "************************************"
-    echo "*       Delete app and env...      *"
-    echo "************************************"
-
-    env=${env_uuid}
-
-    vela delete ${env} -n ${env} -y
-    all_pod_name=`kubectl get pods --no-headers -o custom-columns=":metadata.name" -n ${env}`
-    for pod in $all_pod_name;
-    do
-      kubectl delete pod ${pod} -n ${env}
-    done
-
-    sleep 30
-
-    kubectl proxy &
-    PID=$!
-    sleep 3
-
-    DELETE_ENV=${env}
-
-    vela env delete ${DELETE_ENV} -y
-    sleep 3
-    kubectl delete namespace ${DELETE_ENV} --wait=false
-    kubectl get ns ${DELETE_ENV} -o json | jq '.spec.finalizers=[]' > ns-without-finalizers.json
-    cat ns-without-finalizers.json
-    curl -X PUT http://localhost:8001/api/v1/namespaces/${DELETE_ENV}/finalize -H "Content-Type: application/json" --data-binary @ns-without-finalizers.json
-
-    kill $PID
-fi
-
-if [ ${ACTION} == "try" ]; then
-  kubectl get pods --all-namespaces
-fi
-
-
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..8275ea7
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,171 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.example</groupId>
+  <artifactId>rocketmq-test-tool</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>jar</packaging>
+
+  <name>rocketmq-test-tool</name>
+  <url>http://maven.apache.org</url>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.squareup.okhttp3</groupId>
+      <artifactId>okhttp</artifactId>
+      <version>4.10.0</version>
+    </dependency>
+      <dependency>
+        <groupId>org.json</groupId>
+        <artifactId>json</artifactId>
+        <version>20230227</version>
+      </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-compress</artifactId>
+      <version>1.21</version>
+    </dependency>
+
+    <dependency>
+      <groupId>commons-cli</groupId>
+      <artifactId>commons-cli</artifactId>
+      <version>1.5.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jgit</groupId>
+      <artifactId>org.eclipse.jgit</artifactId>
+      <version>5.8.1.202007141445-r</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.shared</groupId>
+      <artifactId>maven-invoker</artifactId>
+      <version>2.2</version>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+      <version>2.12.7.1</version>
+    </dependency>
+
+    <dependency>
+      <groupId>io.fabric8</groupId>
+      <artifactId>kubernetes-client</artifactId>
+      <version>6.7.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>com.fasterxml.jackson.dataformat</groupId>
+      <artifactId>jackson-dataformat-yaml</artifactId>
+      <version>2.12.3</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.13.1</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.projectlombok</groupId>
+      <artifactId>lombok</artifactId>
+      <version>1.18.8</version>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <version>2.0.0-beta1</version>
+    </dependency>
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+      <version>1.3.0-beta0</version>
+    </dependency>
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-core</artifactId>
+      <version>1.3.0-beta0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.yaml</groupId>
+      <artifactId>snakeyaml</artifactId>
+      <version>2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+      <version>3.8.1</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+      <version>2.4</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-collections</groupId>
+      <artifactId>commons-collections</artifactId>
+      <version>3.2.2</version>
+    </dependency>
+    <dependency>
+      <groupId>com.alibaba</groupId>
+      <artifactId>fastjson</artifactId>
+      <version>1.2.67</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+      <version>20.0</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-codec</groupId>
+      <artifactId>commons-codec</artifactId>
+      <version>1.15</version>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-core</artifactId>
+      <version>5.0.13.RELEASE</version>
+    </dependency>
+
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <archive>
+                <manifest>
+                  <mainClass>
+                    org/apache/process/Main
+                  </mainClass>
+                </manifest>
+              </archive>
+              <descriptorRefs>
+                <descriptorRef>jar-with-dependencies</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.8.0</version>
+        <configuration>
+          <source>1.8</source>
+          <target>1.8</target>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
diff --git a/src/main/java/org/apache/process/Main.java b/src/main/java/org/apache/process/Main.java
new file mode 100644
index 0000000..332b68e
--- /dev/null
+++ b/src/main/java/org/apache/process/Main.java
@@ -0,0 +1,104 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.process.action.Deploy;
+import org.apache.process.action.PortForward;
+import org.apache.process.action.EnvClean;
+import org.apache.process.action.RepoTest;
+import org.apache.process.action.GenerateReport;
+import org.apache.commons.cli.*;
+import org.apache.process.config.Configs;
+import org.apache.process.utils.GetParam;
+import org.apache.process.utils.ConfigUtils;
+
+import java.util.*;
+
+import static org.apache.process.utils.GetParam.parseDeployInput;
+import static org.apache.process.utils.GetParam.yamlToMap;
+
+@Slf4j
+public class Main {
+    public static void main(String[] args) {
+        /* define input parameters */
+        Options options = new Options();
+        options.addOption(Option.builder("yamlString").longOpt("yamlString").argName("yamlString").desc("yaml String").hasArg().required(true).build());
+
+        /* parse input parameters */
+        CommandLine cmd;
+        CommandLineParser parser = new DefaultParser();
+        HelpFormatter helper = new HelpFormatter();
+        HashMap<String, String> paramsMap = null;
+        try {
+            cmd = parser.parse(options, args);
+            GetParam getParam = new GetParam();
+            paramsMap = getParam.setParam(cmd);
+        } catch (ParseException e) {
+            log.info("Params input error! Message: {}", e.getMessage());
+            helper.printHelp("Usage:", options);
+            System.exit(1);
+        }
+
+        try {
+            String inputYamlString = paramsMap.get("yamlString");
+            LinkedHashMap<String, Object> inputMap = yamlToMap(inputYamlString);
+            String action = inputMap.get("action").toString();
+
+
+            ConfigUtils configUtils = new ConfigUtils();
+            String askConfig = configUtils.base64Decoder(inputMap.get("askConfig").toString().replace("\\n", ""));
+            if (inputMap.getOrDefault("velauxUsername", null) != null && inputMap.getOrDefault("velauxPassword", null) != null) {
+                Configs.VELAUX_USERNAME = inputMap.get("velauxUsername").toString();
+                Configs.VELAUX_PASSWORD = inputMap.get("velauxPassword").toString();
+            } else {
+                String authentificationInfo[] = configUtils.getAuthInfoFromConfig(askConfig).split(":");
+                Configs.VELAUX_USERNAME = authentificationInfo[0];
+                Configs.VELAUX_PASSWORD = authentificationInfo[1];
+            }
+            if(inputMap.getOrDefault("projectName", null) != null){
+                Configs.PROJECT_NAME = inputMap.get("projectName").toString();
+            }
+
+            configUtils.setConfig(askConfig);
+            new PortForward().startPortForward(Configs.VELA_NAMESPACE, Configs.VELA_POD_LABELS, Configs.PORT_FROWARD);
+
+            boolean isSuccessed = false;
+            if ("deploy".equals(action)) {
+                HashMap<String, Object> deployMap = parseDeployInput(inputYamlString, "helm");
+                isSuccessed = new Deploy().startDeploy(deployMap);
+            } else if ("test".equals(action)) {
+                isSuccessed = new RepoTest().runTest(inputMap);
+                isSuccessed = new GenerateReport().generateReportMarkDown(inputMap) && isSuccessed;
+            } else if ("clean".equals(action)) {
+                isSuccessed = new EnvClean().clean(inputMap);
+            } else {
+                log.error("Error! Please input action!");
+            }
+
+            if (isSuccessed && Configs.IS_ALL_CASE_SUCCESS) {
+                System.exit(0);
+            }
+        } catch (Exception e) {
+            log.error("Execute error! Message: {}", e.getMessage());
+        }
+        System.exit(1);
+    }
+}
diff --git a/src/main/java/org/apache/process/action/Deploy.java b/src/main/java/org/apache/process/action/Deploy.java
new file mode 100644
index 0000000..dc5aebd
--- /dev/null
+++ b/src/main/java/org/apache/process/action/Deploy.java
@@ -0,0 +1,129 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.action;
+
+import okhttp3.Response;
+import org.apache.process.api.AppActions;
+import org.apache.process.api.AuthAction;
+import org.apache.process.api.EnvActions;
+import org.apache.process.config.Configs;
+import org.apache.process.model.Deploymodel;
+import org.apache.process.utils.PrintInfo;
+import org.json.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class Deploy {
+    public boolean startDeploy(HashMap<String, Object> paramsMap) {
+        log.info("****  Create namespace and deploy...  ****");
+
+        AuthAction authAction = new AuthAction();
+        authAction.setToken("login");
+
+        String namespace = paramsMap.get("namespace").toString();
+        log.info("Generate namespace {}", namespace);
+
+        EnvActions envActions = new EnvActions();
+        String envBodyContent = String.format(Deploymodel.ENV_BODY, namespace, namespace, Configs.PROJECT_NAME, namespace);
+        try (Response response = envActions.createEnv(envBodyContent);) {
+            PrintInfo.printRocketInfo(response, String.format("Generate namespace(%s) success!", namespace));
+        } catch (Exception e) {
+            log.error("Create env Error! Message: {}", e.getMessage());
+            return false;
+        }
+
+        log.info("Generate {} Application", namespace);
+        AppActions appActions = new AppActions();
+
+        authAction.setToken("refresh_token");
+        String componentProperty = paramsMap.get("helm").toString();
+        String bodyContent = String.format(Deploymodel.APPLICATION_BODY_COMPONENT, namespace, Configs.PROJECT_NAME, paramsMap.getOrDefault("velaAppDescription", ""), namespace, namespace, paramsMap.get("repoName"), componentProperty);
+        try (Response createAppResponse = appActions.createApplication(bodyContent)) {
+            PrintInfo.printRocketInfo(createAppResponse, String.format(String.format("Generate %s Application success!", namespace)));
+        } catch (Exception e) {
+            log.error("Create application Error! Message: {}", e.getMessage());
+            return false;
+        }
+
+        log.info("deploy {} Application", namespace);
+
+        String workflowName = "workflow-" + namespace;
+        String deployBodyContent = String.format(Deploymodel.DEPLOY_APP_BODY, workflowName);
+        authAction.setToken("refresh_token");
+        try (Response response = appActions.deployOrUpgradeApplication(namespace, deployBodyContent);) {
+            if (!PrintInfo.printRocketInfo(response, String.format("Deploy %s Application success!", namespace))) {
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("Deploy application Error! Message: {}", e.getMessage());
+            return false;
+        }
+
+        log.info("Query pod {} Application status", namespace);
+        int waitTimes = Integer.parseInt(paramsMap.getOrDefault("waitTimes", "900").toString());
+        LocalDateTime startTime = LocalDateTime.now();
+        Response response = null;
+
+        while (ChronoUnit.SECONDS.between(startTime, LocalDateTime.now()) <= waitTimes) {
+            try {
+                authAction.setToken("refresh_token");
+                response = appActions.getApplicationStatus(namespace, namespace);
+                JSONObject json;
+                if (response.body() != null) {
+                    json = new JSONObject(response.body().string());
+                    response.close();
+                } else {
+                    response.close();
+                    continue;
+                }
+
+                String workflowsStatus = json.getJSONObject("status").getJSONObject("workflow").getString("status");
+                String message = new JSONObject(json.getJSONObject("status").getJSONArray("services").get(0).toString()).getString("message");
+
+                if ("succeeded".equals(workflowsStatus)) {
+                    log.info("Success! Message: " + message);
+                    break;
+                } else if ("executing".equals(workflowsStatus)) {
+                    log.info("Waiting... Message : " + message);
+                    TimeUnit.SECONDS.sleep(5);
+                } else {
+                    log.error("Fail! Message: {}", message);
+                    return false;
+                }
+            } catch (Exception e) {
+                log.error("Error! Message: {}", e.getMessage());
+            }
+        }
+        if (response != null) {
+            response.close();
+        }
+
+        if (ChronoUnit.SECONDS.between(startTime, LocalDateTime.now()) > waitTimes) {
+            log.error("Error! Deploy timeout !");
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/process/action/EnvClean.java b/src/main/java/org/apache/process/action/EnvClean.java
new file mode 100644
index 0000000..1fa9721
--- /dev/null
+++ b/src/main/java/org/apache/process/action/EnvClean.java
@@ -0,0 +1,97 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.action;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientBuilder;
+import io.fabric8.kubernetes.client.KubernetesClientException;
+import org.apache.process.api.AppActions;
+import org.apache.process.api.AuthAction;
+import org.apache.process.api.EnvActions;
+import org.apache.process.utils.PrintInfo;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class EnvClean {
+    public boolean clean(HashMap<String, Object> paramsMap) {
+        log.info("******  Delete app and env...  ******");
+        String namespace = paramsMap.get("namespace").toString();
+
+        boolean result = true;
+        /* delete vela application and namespace */
+        try {
+            AuthAction authAction = new AuthAction();
+            authAction.setToken("login");
+            AppActions appActions = new AppActions();
+            appActions.deleteOAM(namespace, namespace).close();
+            int times = 5;
+            while (times-- > 0) {
+                if (PrintInfo.isResponseSuccess(appActions.deleteApplication(namespace))) {
+                    break;
+                }
+                TimeUnit.SECONDS.sleep(3);
+                authAction.setToken("refresh_token");
+            }
+
+            if (times < 0) {
+                log.warn("vela application {} delete fail!", namespace);
+            } else {
+                log.info("vela application {} delete success!", namespace);
+            }
+
+
+            EnvActions envActions = new EnvActions();
+            times = 5;
+            while (times-- > 0) {
+                if (PrintInfo.isResponseSuccess(envActions.deleteEnv(namespace))) {
+                    break;
+                }
+                TimeUnit.SECONDS.sleep(3);
+                authAction.setToken("refresh_token");
+            }
+
+            if (times < 0) {
+                log.warn("vela application {} delete fail!", namespace);
+            } else {
+                log.info("vela namespace {} delete success!", namespace);
+            }
+
+        } catch (Exception e) {
+            log.error("Fail to delete vela application/namespace! Error message: {}", e.getMessage());
+        }
+
+
+        /* delete kubernetes pods and relevant namespace */
+        for (int retryTimes = 6; retryTimes > 0; retryTimes--) {
+            try (KubernetesClient client = new KubernetesClientBuilder().build()) {
+                client.namespaces().withName(namespace).delete();
+                log.info("Delete namespace {} success!", namespace);
+                break;
+            } catch (KubernetesClientException e) {
+                log.error("Delete namespace fail! Message: {}", e.getMessage());
+                result = false;
+            }
+        }
+        return result;
+    }
+}
diff --git a/src/main/java/org/apache/process/action/GenerateReport.java b/src/main/java/org/apache/process/action/GenerateReport.java
new file mode 100644
index 0000000..e3788d2
--- /dev/null
+++ b/src/main/java/org/apache/process/action/GenerateReport.java
@@ -0,0 +1,79 @@
+package org.apache.process.action;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.process.report_utils.testcase.TaskResult;
+import org.apache.process.report_utils.testcase.xUnitTestResultParser;
+import org.apache.process.utils.ConfigUtils;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+@Slf4j
+public class GenerateReport {
+    /**
+     * genarate markdown by test xml report.
+     *
+     * @param inputMap params map.
+     * @return boolean.
+     */
+    public boolean generateReportMarkDown(LinkedHashMap<String, Object> inputMap) {
+        LinkedHashMap<String, Object> envMap = (LinkedHashMap) inputMap.get("ENV");
+        // set test code base url.
+        String repoBaseUrl = splitHttps(envMap.get("CODE").toString()) + "/tree/" + envMap.get("BRANCH").toString() + "/" + envMap.get("CODE_PATH").toString();
+        // test-report path.
+        String xmlPath = String.format("test_report/root/code/%s/target/surefire-reports", envMap.get("CODE_PATH").toString());
+        List<File> fileList = new ArrayList<>();
+        File filePath = new File(xmlPath);
+        // filter .xml format files.
+        String[] files = filePath.list((dir, name) -> {
+            return name.endsWith(".xml");
+        });
+        if (files != null) {
+            for (String file : files) {
+                fileList.add(new File(xmlPath + "/" + file));
+            }
+        } else {
+            log.error("xml files unfounded!");
+            return false;
+        }
+        // parse test files.
+        xUnitTestResultParser parser = new xUnitTestResultParser();
+        TaskResult res = parser.parseTestResult(fileList);
+        File f = new File("result.md");
+        FileWriter fw;
+        try {
+            fw = new FileWriter(f);
+            String githubToken = "";
+            if (envMap.containsKey("GITHUB_TOKEN")) {
+                githubToken = "token " + new ConfigUtils().base64Decoder(envMap.get("GITHUB_TOKEN").toString().replace("\\n", ""));
+            }
+            String str = res.toMarkdown(envMap.get("REPO_NAME").toString(), repoBaseUrl, envMap.get("BRANCH").toString(), envMap.get("CODE_PATH").toString(), githubToken.replace("\n", ""));
+            fw.write(str);
+            fw.close();
+            log.info("Generate report success!");
+            return true;
+        } catch (IOException e) {
+            log.error("Fail to generate report! Error message: {}", e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * if test code url contains proxy address, remove it.
+     *
+     * @param url test code url
+     * @return test code url which hasn't proxy address.
+     */
+    public String splitHttps(String url) {
+        String[] httpUrl = url.split("https://");
+        if (httpUrl.length == 1) {
+            return httpUrl[0];
+        } else {
+            return "https://" + httpUrl[httpUrl.length - 1];
+        }
+    }
+}
diff --git a/src/main/java/org/apache/process/action/PortForward.java b/src/main/java/org/apache/process/action/PortForward.java
new file mode 100644
index 0000000..6abfc5d
--- /dev/null
+++ b/src/main/java/org/apache/process/action/PortForward.java
@@ -0,0 +1,42 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.action;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.process.api.FabricPortForward;
+
+@Slf4j
+public class PortForward {
+    public void startPortForward(String namespace, String podLabels, int localPort) throws InterruptedException {
+        FabricPortForward fabricPortForward = new FabricPortForward();
+        log.info("Start port forward....");
+        Thread thread = new Thread(
+                () -> {
+                    try {
+                        fabricPortForward.podPortForward(namespace, podLabels, localPort);
+                    } catch (Exception ex) {
+                        ex.printStackTrace();
+                    }
+                });
+        thread.start();
+
+        Thread.sleep(5000);
+    }
+}
diff --git a/src/main/java/org/apache/process/action/QueryTestPod.java b/src/main/java/org/apache/process/action/QueryTestPod.java
new file mode 100644
index 0000000..2651888
--- /dev/null
+++ b/src/main/java/org/apache/process/action/QueryTestPod.java
@@ -0,0 +1,195 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.action;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientBuilder;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.process.api.ExecuteCMD;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class QueryTestPod {
+    /**
+     * get dictionary and file about test result from pod.
+     *
+     * @param testPodName  test pod name.
+     * @param namespace    pod namespace.
+     * @param testCodePath test code path.
+     * @return boolean.
+     * @throws IOException          io exception.
+     * @throws InterruptedException interrupt exception.
+     */
+    public boolean getPodResult(String testPodName, String namespace, String testCodePath, int waitTimes) throws IOException, InterruptedException {
+        log.info("****   query status and get result   ****");
+
+        String podStatus = null;
+        try (KubernetesClient client = new KubernetesClientBuilder().build()) {
+            podStatus = client.pods().inNamespace(namespace).withName(testPodName).get().getStatus().getPhase();
+        } catch (Exception ignored) {
+            log.warn("PodStatus set Pending..");
+        }
+
+        if (podStatus == null) {
+            podStatus = "Pending";
+        }
+        // mark if program has been executed.
+        boolean isWaitingTest = true;
+        LocalDateTime startTime = LocalDateTime.now();
+        while ("Pending".equals(podStatus) || "Running".equals(podStatus) || isWaitingTest) {
+            TimeUnit.SECONDS.sleep(5);
+            try (KubernetesClient client = new KubernetesClientBuilder().build()) {
+                podStatus = client.pods().inNamespace(namespace).withName(testPodName).get().getStatus().getPhase();
+            } catch (Exception e) {
+                log.warn("Query pod fail! Errormessage: {}. Retry again...", e.getMessage());
+            }
+
+            if (podStatus == null) {
+                podStatus = "Pending";
+            }
+            if (isWaitingTest) {
+                log.info("Waiting for {} test done...", testPodName);
+            } else {
+                log.info("Current pod status is {}, waiting pod stop...", podStatus);
+            }
+
+            // Check if the execution of the test program has ended.
+            if (isWaitingTest) {
+                String cmdOutput = null;
+                try (ExecuteCMD executeCMD = new ExecuteCMD()) {
+                    cmdOutput = executeCMD.execCommandOnPod(testPodName, namespace, "/bin/bash", "-c", "ls /root | grep testdone\n");
+                } catch (Exception e) {
+                    log.warn("Query error! Error message: %s. Continue to query..." + e.getMessage());
+                }
+
+                boolean isTimeout = ChronoUnit.SECONDS.between(startTime, LocalDateTime.now()) >= waitTimes;
+                if (isTimeout) {
+                    log.info("Current pod timeout! Stop pod...");
+                }
+
+                // if the test program ends, get the result.
+                if ((cmdOutput != null && cmdOutput.contains("testdone")) || isTimeout) {
+                    log.info("test done !");
+                    isWaitingTest = false;
+
+                    Path filePath = Paths.get("testlog.txt");
+                    if (!Files.exists(filePath)) {
+                        Files.createFile(filePath);
+                    }
+                    int downloaTimes = 5;
+                    while (downloaTimes-- > 0) {
+                        if (downloadFile(namespace, testPodName, testPodName, "/root/testlog.txt", filePath)) {
+                            break;
+                        }
+                    }
+
+                    Path dirPath = Paths.get("test_report");
+                    if (!Files.exists(dirPath)) {
+                        Files.createDirectory(dirPath);
+                    }
+                    downloaTimes = 5;
+                    while (downloaTimes-- > 0) {
+                        if (downloadDir(namespace, testPodName, testPodName, String.format("/root/code/%s/target/surefire-reports", testCodePath), dirPath)) {
+                            break;
+                        }
+                    }
+                }
+
+                if (isTimeout) {
+                    podStatus = "Failed";
+                    break;
+                }
+            }
+        }
+
+        try (KubernetesClient client = new KubernetesClientBuilder().build()) {
+            client.pods().inNamespace(namespace).withName(testPodName).delete();
+            log.info("Delete test pod: {} success !", testPodName);
+        } catch (Exception e) {
+            log.warn("Delete test pod {} error !", testPodName);
+        }
+
+        log.info("Test status: " + podStatus);
+        return !"Failed".equals(podStatus);
+    }
+
+    /**
+     * download file from pod.
+     *
+     * @param namespace     pod namespace.
+     * @param podName       pod name.
+     * @param containerName pod's container name.
+     * @param srcPath       file path in pod.
+     * @param targetPath    target file path.
+     * @return boolean.
+     */
+    public boolean downloadFile(String namespace, String podName, String containerName, String srcPath, Path targetPath) {
+        try (KubernetesClient client = new KubernetesClientBuilder().build()) {
+            client.pods().inNamespace(namespace).withName(podName).inContainer(containerName).file(srcPath).copy(targetPath);
+            log.info("File({}) copied successfully!", srcPath);
+            return true;
+        } catch (Exception e) {
+            log.error("Fail to get {}! Error message: {}", srcPath, e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * download dictionary from pod.
+     *
+     * @param namespace     pod namespace.
+     * @param podName       pod name.
+     * @param containerName pod's container name.
+     * @param srcPath       dictionary path in pod
+     * @param tarPath       target dictionary path.
+     * @return boolean.
+     */
+    public boolean downloadDir(String namespace, String podName, String containerName, String srcPath, Path tarPath) {
+        try (KubernetesClient client = new KubernetesClientBuilder().build()) {
+            client.pods().inNamespace(namespace).withName(podName).inContainer(containerName).dir(srcPath).copy(tarPath);
+            TimeUnit.SECONDS.sleep(2);
+            client.close();
+            String xmlPath = tarPath + "/" + srcPath;
+            File filePath = new File(xmlPath);
+            String[] files = filePath.list((dir, name) -> {
+                return name.endsWith(".xml");
+            });
+            if (files != null) {
+                log.info("Directory({}) copied successfully!", srcPath);
+                return true;
+            } else {
+                log.warn("Directory({}) copied fail! ", srcPath);
+                return false;
+            }
+
+        } catch (Exception e) {
+            log.error("Fail to get {}! Error message: {}", srcPath, e.getMessage());
+            return false;
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/process/action/RepoTest.java b/src/main/java/org/apache/process/action/RepoTest.java
new file mode 100644
index 0000000..7c94c5d
--- /dev/null
+++ b/src/main/java/org/apache/process/action/RepoTest.java
@@ -0,0 +1,112 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.action;
+
+
+import io.fabric8.kubernetes.api.model.*;
+import io.fabric8.kubernetes.client.*;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.util.*;
+
+@Slf4j
+public class RepoTest {
+
+    public boolean runTest(LinkedHashMap<String, Object> inputMap) throws IOException, InterruptedException {
+        String namespace = inputMap.get("namespace").toString();
+        String testPodName = "test-" + namespace + "-" + new Random().nextInt(100000);
+        log.info("****    E2E TEST...    ****");
+        log.info("namespace: {}, test pod name: {}", namespace, testPodName);
+
+        LinkedHashMap<String, Object> containerMap = (LinkedHashMap) inputMap.get("CONTAINER");
+        // build resource limits
+        LinkedHashMap<String, Object> limitsResourceMap = (LinkedHashMap) containerMap.get("RESOURCE_LIMITS");
+        HashMap<String, Quantity> resourcesLimits = new HashMap<>();
+        for (String limitKey : limitsResourceMap.keySet()) {
+            resourcesLimits.put(limitKey, new Quantity(limitsResourceMap.get(limitKey).toString()));
+        }
+        // build resource requests
+        LinkedHashMap<String, Object> requestResourceMap = (LinkedHashMap) containerMap.get("RESOURCE_REQUIRE");
+        HashMap<String, Quantity> resourceRequests = new HashMap<>();
+        for (String requestKey : requestResourceMap.keySet()) {
+            resourceRequests.put(requestKey, new Quantity(requestResourceMap.get(requestKey).toString()));
+        }
+        // biild env
+        LinkedHashMap<String, Object> envMap = (LinkedHashMap) inputMap.get("ENV");
+        if (!envMap.containsValue("ALL_IP") || envMap.get("ALL_IP") == null || "null".equals(envMap.get("ALL_IP"))) {
+            /* get all IP */
+            try (KubernetesClient client = new DefaultKubernetesClient()) {
+                List<Pod> pods = client.pods().inNamespace(namespace).list().getItems();
+                StringBuilder allIP = new StringBuilder();
+                for (Pod pod : pods) {
+                    allIP.append(pod.getMetadata().getName()).append(":").append(pod.getStatus().getPodIP()).append(",");
+                }
+                if (allIP.length() == 0) {
+                    log.error("No pod found in current namespace: {}. Please check the namespace name and the pod name.", namespace);
+                    return false;
+                }
+                envMap.put("ALL_IP", allIP.substring(0, allIP.length() - 1));
+            }
+        }
+        // set env elements
+        EnvVar[] envVars = new EnvVar[envMap.size()];
+        int envItemIndex = 0;
+        for (String envKey : envMap.keySet()) {
+            envVars[envItemIndex] = new EnvVar(envKey, envMap.get(envKey).toString(), null);
+            envItemIndex++;
+        }
+
+        try (final KubernetesClient client = new KubernetesClientBuilder().build()) {
+            Pod pod = new PodBuilder()
+                    .withApiVersion(inputMap.getOrDefault("API_VERSION", "v1").toString())
+                    .withKind(inputMap.getOrDefault("KIND", "Pod").toString())
+                    .withNewMetadata()
+                    .withName(testPodName)
+                    .withNamespace(namespace)
+                    .endMetadata()
+                    .withNewSpec()
+                    .withRestartPolicy(inputMap.getOrDefault("RESTART_POLICY", "Never").toString())
+                    .withContainers(new ContainerBuilder()
+                            .withName(testPodName)
+                            .withImage(containerMap.get("IMAGE").toString())
+                            .withResources(new ResourceRequirementsBuilder()
+                                    .withRequests(resourceRequests)
+                                    .withLimits(resourcesLimits)
+                                    .build()
+                            )
+                            .withEnv(envVars)
+                            .build())
+                    .endSpec()
+                    .build();
+
+            for (int retryTimes = 3; retryTimes > 0; retryTimes--) {
+                try {
+                    // 使用KubernetesClient创建Pod
+                    client.pods().inNamespace(namespace).resource(pod).create();
+                    break;
+                } catch (Exception e) {
+                    log.error("create pod {} failed, retry again...", testPodName);
+                }
+            }
+        }
+        return new QueryTestPod().getPodResult(testPodName, namespace, envMap.get("CODE_PATH").toString(), Integer.parseInt(envMap.getOrDefault("WAIT_TIME", "900").toString()));
+    }
+}
diff --git a/src/main/java/org/apache/process/api/AppActions.java b/src/main/java/org/apache/process/api/AppActions.java
new file mode 100644
index 0000000..7c5413c
--- /dev/null
+++ b/src/main/java/org/apache/process/api/AppActions.java
@@ -0,0 +1,102 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.api;
+
+import okhttp3.*;
+import org.apache.process.config.Configs;
+
+import java.io.IOException;
+
+public class AppActions {
+    public static final String APP_API = "applications";
+    private final OkHttpClient client;
+    private final String URL;
+    private final MediaType mediaType;
+
+    public AppActions() {
+        client = new OkHttpClient();
+        URL = "http://" + Configs.IP + "/" + Configs.KUBEVELA_API + "/" + APP_API;
+        mediaType = MediaType.parse("application/json");
+    }
+
+    public Response createApplication(String bodyContent) throws IOException {
+        RequestBody body = RequestBody.create(mediaType, bodyContent);
+        Request request = new Request.Builder()
+                .url(URL)
+                .post(body)
+                .addHeader("Content-Type", "application/json")
+                .addHeader("Accept", "application/json, application/xml")
+                .addHeader("Authorization", Configs.Authorization)
+                .build();
+
+        return client.newCall(request).execute();
+    }
+
+    public Response deployOrUpgradeApplication(String appName, String bodyContent) throws IOException {
+        String url = URL + "/" + appName + "/deploy";
+
+        RequestBody body = RequestBody.create(mediaType, bodyContent);
+        Request request = new Request.Builder()
+                .url(url)
+                .post(body)
+                .addHeader("Content-Type", "application/json")
+                .addHeader("Accept", "application/json, application/xml")
+                .addHeader("Authorization", Configs.Authorization)
+                .build();
+
+        return client.newCall(request).execute();
+    }
+
+    public Response getApplicationStatus(String appName, String envName) throws IOException {
+        String url = URL + "/" + appName + "/envs/" + envName + "/status";
+        Request request = new Request.Builder()
+                .url(url)
+                .get()
+                .addHeader("Accept", "application/json, application/xml")
+                .addHeader("Authorization", Configs.Authorization)
+                .build();
+
+        return client.newCall(request).execute();
+    }
+
+    public Response deleteApplication(String appName) throws IOException {
+        String url = URL + "/" + appName;
+        Request request = new Request.Builder()
+                .url(url)
+                .delete(null)
+                .addHeader("Content-Type", "application/json")
+                .addHeader("Accept", "application/json, application/xml")
+                .addHeader("Authorization", Configs.Authorization)
+                .build();
+
+        return client.newCall(request).execute();
+    }
+
+    public Response deleteOAM(String namespace, String appName) throws IOException {
+        String url = "http://" + Configs.IP + "/v1/namespaces/" + namespace + "/applications/" + appName;
+        Request request = new Request.Builder()
+                .url(url)
+                .delete(null)
+                .addHeader("Authorization", Configs.Authorization)
+                .build();
+
+        return client.newCall(request).execute();
+    }
+}
diff --git a/src/main/java/org/apache/process/api/AuthAction.java b/src/main/java/org/apache/process/api/AuthAction.java
new file mode 100644
index 0000000..dbc144d
--- /dev/null
+++ b/src/main/java/org/apache/process/api/AuthAction.java
@@ -0,0 +1,83 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.api;
+
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.apache.process.config.Configs;
+import org.json.JSONObject;
+
+import java.util.Objects;
+
+@Slf4j
+public class AuthAction {
+    public static final String APP_API = "auth";
+    private final String URL;
+    private final OkHttpClient client;
+
+    public AuthAction() {
+        client = new OkHttpClient();
+        URL = "http://" + Configs.IP + "/" + Configs.KUBEVELA_API + "/" + APP_API;
+    }
+
+    public void setToken(String action) {
+
+        Request request = null;
+        if ("login".equals(action)) {
+            String url = URL + "/" + "login";
+            String bodyContent = String.format("{\n \"code\": \"string\",\n \"password\": \"%s\",\n  \"username\": \"%s\"\n}", Configs.VELAUX_PASSWORD, Configs.VELAUX_USERNAME);
+            RequestBody body = RequestBody.create(MediaType.parse("application/json"), bodyContent);
+            request = new Request.Builder()
+                    .url(url)
+                    .post(body)
+                    .addHeader("Content-Type", "application/json")
+                    .addHeader("Accept", "application/json, application/xml")
+                    .build();
+        } else {
+            String url = URL + "/" + "refresh_token";
+            request = new Request.Builder()
+                    .url(url)
+                    .get()
+                    .addHeader("Accept", "application/json, application/xml")
+                    .addHeader("accessToken", Configs.TOKEN)
+                    .addHeader("refreshToken", Configs.REFRESH_TOKEN)
+                    .build();
+        }
+
+        int retryTimes = 3;
+        boolean isSuccess = false;
+        while (retryTimes-- > 0 && !isSuccess) {
+            try (Response response = client.newCall(request).execute()) {
+                JSONObject json = new JSONObject(response.body().string());
+                if (json.has("accessToken") && !Objects.equals(json.getString("accessToken"), "")) {
+                    Configs.TOKEN = json.getString("accessToken");
+                    Configs.Authorization = "Bearer " + Configs.TOKEN;
+                    Configs.REFRESH_TOKEN = json.getString("refreshToken");
+                    isSuccess = true;
+                } else {
+                    log.info("{} fail! ", action);
+                }
+            } catch (Exception e) {
+                log.error("{} error! ", action);
+            }
+        }
+
+    }
+}
diff --git a/src/main/java/org/apache/process/api/EnvActions.java b/src/main/java/org/apache/process/api/EnvActions.java
new file mode 100644
index 0000000..84ed855
--- /dev/null
+++ b/src/main/java/org/apache/process/api/EnvActions.java
@@ -0,0 +1,64 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.api;
+
+import okhttp3.*;
+import org.apache.process.config.Configs;
+
+import java.io.IOException;
+
+public class EnvActions {
+    public static final String APP_API = "envs";
+    private final OkHttpClient client;
+    public String URL;
+
+    public EnvActions() {
+        client = new OkHttpClient();
+        URL = "http://" + Configs.IP + "/" + Configs.KUBEVELA_API + "/" + APP_API;
+    }
+
+    public Response createEnv(String bodyContent) throws IOException {
+        RequestBody body = RequestBody.create(MediaType.parse("application/json"), bodyContent);
+
+        Request request = new Request.Builder()
+                .url(URL)
+                .post(body)
+                .addHeader("Content-Type", "application/json")
+                .addHeader("Accept", "application/json, application/xml")
+                .addHeader("Authorization", Configs.Authorization)
+                .build();
+
+        return client.newCall(request).execute();
+    }
+
+    public Response deleteEnv(String namespace) throws IOException {
+        String url = URL + "/" + namespace;
+
+        Request request = new Request.Builder()
+                .url(url)
+                .delete(null)
+                .addHeader("Accept", "application/json, application/xml")
+                .addHeader("Authorization", Configs.Authorization)
+                .build();
+
+        return client.newCall(request).execute();
+    }
+
+}
diff --git a/src/main/java/org/apache/process/api/ExecuteCMD.java b/src/main/java/org/apache/process/api/ExecuteCMD.java
new file mode 100644
index 0000000..3e6f228
--- /dev/null
+++ b/src/main/java/org/apache/process/api/ExecuteCMD.java
@@ -0,0 +1,92 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.api;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.client.KubernetesClientBuilder;
+import io.fabric8.kubernetes.client.dsl.ExecListener;
+import io.fabric8.kubernetes.client.dsl.ExecWatch;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.ByteArrayOutputStream;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class ExecuteCMD implements AutoCloseable {
+    private final KubernetesClient client;
+
+    @SuppressWarnings("java:S106")
+    public ExecuteCMD() {
+        this.client = new KubernetesClientBuilder().build();
+    }
+
+    @SneakyThrows
+    public String execCommandOnPod(String podName, String namespace, String... cmd) {
+        Pod pod = client.pods().inNamespace(namespace).withName(podName).get();
+        CompletableFuture<String> data = new CompletableFuture<>();
+        try (ExecWatch execWatch = execCmd(pod, data, cmd)) {
+            return data.get(20, TimeUnit.SECONDS);
+        }
+    }
+
+    @Override
+    public void close() {
+        client.close();
+    }
+
+    private ExecWatch execCmd(Pod pod, CompletableFuture<String> data, String... command) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        return client.pods()
+                .inNamespace(pod.getMetadata().getNamespace())
+                .withName(pod.getMetadata().getName())
+                .writingOutput(baos)
+                .writingError(baos)
+                .usingListener(new SimpleListener(data, baos))
+                .exec(command);
+    }
+
+    static class SimpleListener implements ExecListener {
+
+        private CompletableFuture<String> data;
+        private ByteArrayOutputStream baos;
+
+        public SimpleListener(CompletableFuture<String> data, ByteArrayOutputStream baos) {
+            this.data = data;
+            this.baos = baos;
+        }
+
+        @Override
+        public void onOpen() {
+        }
+
+        @Override
+        public void onFailure(Throwable t, Response failureResponse) {
+            data.completeExceptionally(t);
+        }
+
+        @Override
+        public void onClose(int code, String reason) {
+            data.complete(baos.toString());
+        }
+    }
+}
diff --git a/src/main/java/org/apache/process/api/FabricPortForward.java b/src/main/java/org/apache/process/api/FabricPortForward.java
new file mode 100644
index 0000000..633972a
--- /dev/null
+++ b/src/main/java/org/apache/process/api/FabricPortForward.java
@@ -0,0 +1,78 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.api;
+
+
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.api.model.PodList;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientBuilder;
+import io.fabric8.kubernetes.client.LocalPortForward;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import org.apache.process.config.Configs;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class FabricPortForward {
+    public void podPortForward(String namespace, String podLabels, int localPort) {
+
+        try (KubernetesClient client = new KubernetesClientBuilder().build()) {//.withConfig(config)
+            PodList pode = client.pods().inNamespace(namespace).list();
+            for (Pod p : pode.getItems()) {
+                String labels = p.getMetadata().getLabels().get("app.oam.dev/name");
+                if (podLabels.equals(labels)) {
+                    int containerPort = p.getSpec().getContainers().get(0).getPorts().get(0).getContainerPort();
+                    client.pods().inNamespace(namespace).withName(p.getMetadata().getName()).waitUntilReady(10, TimeUnit.SECONDS);
+
+                    InetAddress inetAddress = InetAddress.getByAddress(new byte[]{127, 0, 0, 1});
+                    LocalPortForward portForward = client.pods().inNamespace(namespace).withName(p.getMetadata().getName()).portForward(containerPort,
+                            inetAddress, localPort);
+
+                    log.info("Checking forwarded port......");
+                    int times = 5;
+                    boolean isForwarded = true;
+                    while (isForwarded && times-- > 0) {
+                        try {
+                            new OkHttpClient()
+                                    .newCall(new Request.Builder().get().url("http://127.0.0.1:" + portForward.getLocalPort()).build()).execute()
+                                    .body();
+                            log.info("check forwarded port success! ");
+                            isForwarded = false;
+                        } catch (IOException e) {
+                            log.error("check forwarded port fail! retry... ");
+                        }
+                        ;
+                    }
+                    TimeUnit.MINUTES.sleep(Configs.MAX_RUN_TIME);
+                    log.info("Closing forwarded port");
+                    portForward.close();
+                }
+            }
+        } catch (IOException | InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+}
+
diff --git a/src/main/java/org/apache/process/config/Configs.java b/src/main/java/org/apache/process/config/Configs.java
new file mode 100644
index 0000000..f639c27
--- /dev/null
+++ b/src/main/java/org/apache/process/config/Configs.java
@@ -0,0 +1,55 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.config;
+
+public class Configs {
+
+    // velaux bearer token
+    public static String  Authorization = "";
+
+    // velaux token
+    public static String  TOKEN = "";
+
+    // velaux refresh token
+    public static String REFRESH_TOKEN = "";
+
+    // velaux username
+    public static String VELAUX_USERNAME = "";
+
+    // velaux password
+    public static String VELAUX_PASSWORD = "";
+
+    // kubevela api version
+    public static final String KUBEVELA_API = "api/v1";
+
+    // Maximum time(MINUTES) required for testing.
+    public static final int MAX_RUN_TIME = 30;
+
+    public static String PROJECT_NAME = "wyftest";
+
+    public static String VELA_NAMESPACE = "vela-system";
+    public static String VELA_POD_LABELS = "addon-velaux";
+    public static boolean IS_ALL_CASE_SUCCESS = true;
+    // open port
+    public static final int PORT_FROWARD = 9082;
+    // velaux IP
+    public static final String IP = "127.0.0.1:"+PORT_FROWARD;
+
+}
diff --git a/src/main/java/org/apache/process/model/Deploymodel.java b/src/main/java/org/apache/process/model/Deploymodel.java
new file mode 100644
index 0000000..9e81930
--- /dev/null
+++ b/src/main/java/org/apache/process/model/Deploymodel.java
@@ -0,0 +1,85 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.model;
+
+import org.json.JSONObject;
+import org.yaml.snakeyaml.Yaml;
+
+import java.util.Map;
+
+public class Deploymodel {
+    public static final String APPLICATION_BODY_COMPONENT = "{\n" +
+            "    \"name\": \"%s\",\n" +
+            "    \"project\": \"%s\",\n" +
+            "    \"description\": \"%s\",\n" +
+            "    \"alias\": \"%s\",\n" +
+            "    \"envBinding\": [\n" +
+            "        {\n" +
+            "            \"name\": \"%s\"\n" +
+            "        }\n" +
+            "    ],\n" +
+            "    \"component\": {\n" +
+            "        \"name\": \"%s\",\n" +
+            "        \"componentType\": \"helm\",\n" +
+            "        \"properties\": \"%s\"" +
+            "    }\n" +
+            "}";
+
+
+    public static final String ENV_BODY = "{\n" +
+            "  \"allowTargetConflict\": true,\n" +
+            "  \"description\": \"create env for test\",\n" +
+            "  \"name\": \"%s\",\n" +
+            "  \"namespace\": \"%s\",\n" +
+            "  \"project\": \"%s\",\n" +
+            "  \"alias\": \"%s\"\n" +
+            "}";
+
+    public static final String DEPLOY_APP_BODY = "{\n" +
+            "  \"force\": true,\n" +
+            "  \"note\": \"test deploy by http request\",\n" +
+            "  \"triggerType\": \"webhook\",\n" +
+            "  \"workflowName\": \"%s\"\n" +
+            "}";
+
+    public static String generateComponentProperties(String helmValue, String chartPath, String chartBranch, String chartGit) {
+        StringBuilder builder = new StringBuilder();
+        String baseString =
+                "chart: %s\n" +
+                        "git:\n" +
+                        "  branch: %s\n" +
+                        "repoType: git\n" +
+                        "retries: 3\n" +
+                        "url: %s\n" +
+                        "values:\n";
+        builder.append(String.format(baseString, chartPath, chartBranch, chartGit));
+        String[] lines = helmValue.split("\n");
+        for (String line : lines) {
+            builder.append("  ").append(line).append("\n");
+        }
+        Yaml yaml = new Yaml();
+
+        Map<String, Object> map = (Map<String, Object>) yaml.load(builder.toString());
+        JSONObject jsonObject = new JSONObject(map);
+        return jsonObject.toString().replaceAll("\"", "\\\\\"");
+    }
+
+
+}
diff --git a/src/main/java/org/apache/process/report_utils/FileUtils.java b/src/main/java/org/apache/process/report_utils/FileUtils.java
new file mode 100644
index 0000000..48fa093
--- /dev/null
+++ b/src/main/java/org/apache/process/report_utils/FileUtils.java
@@ -0,0 +1,27 @@
+package org.apache.process.report_utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
+import java.io.*;
+
+/**
+ * Created by wangtong.wt on 2017/3/20.
+ *
+ * @author wangtong.wt
+ * @date 2017/03/20
+ */
+@Slf4j
+public class FileUtils {
+    public static String getFileMd5(File file) {
+        try {
+            InputStream inputStream = new FileInputStream(file);
+            return DigestUtils.md5Hex(inputStream);
+        } catch (Exception e) {
+            log.error(ExceptionUtils.getStackTrace(e));
+        }
+        return null;
+    }
+
+}
diff --git a/src/main/java/org/apache/process/report_utils/GetGithubRepoInfo.java b/src/main/java/org/apache/process/report_utils/GetGithubRepoInfo.java
new file mode 100644
index 0000000..a1d2d8e
--- /dev/null
+++ b/src/main/java/org/apache/process/report_utils/GetGithubRepoInfo.java
@@ -0,0 +1,160 @@
+package org.apache.process.report_utils;
+
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.Base64;
+import java.util.HashMap;
+
+@Slf4j
+public class GetGithubRepoInfo {
+    public static final String API_BASE_URL = "https://api.github.com/repos";
+
+    private final OkHttpClient httpClient;
+
+    public GetGithubRepoInfo() {
+        this.httpClient = new OkHttpClient();
+    }
+
+    /**
+     * find all files in current path.
+     *
+     * @param url         repository http url.
+     * @param gitBranch   reposity branch.
+     * @param githubToken github token.
+     * @param fileMap     file Map.
+     */
+    public void getAllFilePath(String url, String gitBranch, String githubToken, HashMap<String, RepoFileInfo> fileMap) {
+        Request request;
+        if (githubToken != null && githubToken.length() != 0) {
+            request = new Request.Builder()
+                    .url(url + "?ref=" + gitBranch)
+                    .addHeader("Authorization", githubToken)
+                    .build();
+        } else {
+            request = new Request.Builder()
+                    .url(url + "?ref=" + gitBranch)
+                    .build();
+        }
+
+        try (Response response = httpClient.newCall(request).execute()) {
+            if (response.isSuccessful()) {
+                JSONArray jsonArray = new JSONArray(response.body().string());
+                for (int i = 0; i < jsonArray.length(); i++) {
+                    JSONObject jsonObject = jsonArray.getJSONObject(i);
+                    String type = jsonObject.getString("type");
+                    String fileUrl = jsonObject.getString("html_url");
+                    String[] array = jsonObject.getString("path").split("/");
+                    String filename = array[array.length - 1];
+                    if ("dir".equals(type)) {
+                        getAllFilePath(url + "/" + filename, gitBranch, githubToken, fileMap);
+                    } else {
+                        RepoFileInfo repoFileInfo = new RepoFileInfo();
+                        repoFileInfo.setFileName(filename);
+                        repoFileInfo.setFileUrl(fileUrl);
+                        if (filename.split("\\.").length == 2) {
+                            repoFileInfo.setSuffix(filename.split("\\.")[1]);
+                            fileMap.put(filename.split("\\.")[0], repoFileInfo);
+                        } else {
+                            fileMap.put(filename, repoFileInfo);
+                        }
+                    }
+                }
+            } else if (response.code() == 404) {
+                log.error("Directory does not exist!");
+            } else {
+                log.error("Error! response code: {}, response message: {}", response.code(), response.message());
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * find target function lines.
+     *
+     * @param className   class name.
+     * @param url         repository http url.
+     * @param keyword     function name.
+     * @param githubToken github token.
+     * @param fileInfoMap file info map.
+     * @return lines number.
+     * @throws IOException not found exception.
+     */
+    public int getFunctionRowFromFile(String className, String url, String keyword, String githubToken, HashMap<String, RepoFileInfo> fileInfoMap) throws IOException {
+        if (fileInfoMap.get(className).getContent() == null) {
+            Request request;
+            if (githubToken != null && githubToken.length() != 0) {
+                request = new Request.Builder()
+                        .url(url)
+                        .addHeader("Authorization", githubToken)
+                        .build();
+            } else {
+                request = new Request.Builder()
+                        .url(url)
+                        .build();
+            }
+
+            Response response = httpClient.newCall(request).execute();
+
+            if (!response.isSuccessful()) {
+                throw new IOException("Unexpected code " + response);
+            }
+            String responseBody = response.body().string();
+            JSONObject jsonObject = new JSONObject(responseBody);
+            String s = jsonObject.getString("content");
+            fileInfoMap.get(className).setContent(s.replace("\n", ""));
+        }
+        String content = new String(Base64.getDecoder().decode(fileInfoMap.get(className).getContent()));
+
+        String[] lines = content.split("\n");
+        int result = 0;
+
+        for (int i = 0; i < lines.length; i++) {
+            if (lines[i].contains(keyword)) {
+                result = i + 1;
+                break;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * get URL of the test case.
+     *
+     * @param fileInfoMap  class to fileInfo map.
+     * @param githubToken  github token.
+     * @param className    test class name.
+     * @param functionName test function.
+     * @param repo         username/repository.
+     * @param branch       repository branch.
+     * @return complete url.
+     */
+    public String getCaseUrl(HashMap<String, RepoFileInfo> fileInfoMap, String githubToken, String className, String functionName, String repo, String branch) throws IOException {
+        String url = "";
+        int row = 0;
+        if (fileInfoMap.containsKey(className)) {
+            String[] array = fileInfoMap.get(className).getFileUrl().split(repo);
+            String fileUrl = API_BASE_URL + "/" + repo + "/contents" + array[1].split(branch)[1];
+            row = getFunctionRowFromFile(className, fileUrl, functionName, githubToken, fileInfoMap);
+            url = fileInfoMap.get(className).getFileUrl() + "#L" + row;
+        } else {
+            for (String fileName : fileInfoMap.keySet()) {
+
+                String[] array = fileInfoMap.get(fileName).getFileUrl().split(repo);
+                String fileUrl = API_BASE_URL + "/" + repo + "/contents" + array[1].split(branch, 2)[1];
+                row = getFunctionRowFromFile(fileName, fileUrl, functionName, githubToken, fileInfoMap);
+                if (row != 0) {
+                    url = fileInfoMap.get(fileName).getFileUrl() + "#L" + row;
+                    break;
+                }
+            }
+        }
+        return url;
+    }
+}
diff --git a/src/main/java/org/apache/process/report_utils/MarkdownBuilder.java b/src/main/java/org/apache/process/report_utils/MarkdownBuilder.java
new file mode 100644
index 0000000..7c4df13
--- /dev/null
+++ b/src/main/java/org/apache/process/report_utils/MarkdownBuilder.java
@@ -0,0 +1,67 @@
+package org.apache.process.report_utils;
+
+/**
+ * Created by wangtong.wt on 2017/3/20.
+ *
+ * @author wangtong.wt
+ * @date 2017/03/20
+ */
+public class MarkdownBuilder {
+
+    private StringBuilder stringBuilder = new StringBuilder();
+
+    private MarkdownBuilder(){}
+
+    public static MarkdownBuilder builder() {
+        return new MarkdownBuilder();
+    }
+
+    public MarkdownBuilder addHeader(String content, int level) {
+        StringBuilder header = new StringBuilder();
+        if (level > 6) {
+            level = 6;
+        }
+        if (level <= 0) {
+            level = 1;
+        }
+        for (int i = 0; i < level; i++) {
+            header.append("#");
+        }
+        header.append(" ").append(content);
+        stringBuilder.append(header).append("\n\n");
+        return this;
+    }
+
+    public MarkdownBuilder newLine() {
+        stringBuilder.append("\n");
+        return this;
+    }
+
+    public MarkdownBuilder addBoldText(String content) {
+        stringBuilder.append("**").append(content).append("**");
+        return this;
+    }
+
+    public MarkdownBuilder addCollapse(String title, String msg) {
+        stringBuilder.append("<details>").append("\n");
+        stringBuilder.append("<summary>").append(title).append("</summary>").append("\n");
+        stringBuilder.append("\n").append(msg).append("\n");
+        stringBuilder.append("</details>").append("\n\n");
+
+        return this;
+    }
+
+    public MarkdownBuilder addTable(MarkdownTableBuilder tableBuilder){
+        stringBuilder.append("\n").append(tableBuilder.build()).append("\n\n");
+        return this;
+    }
+
+    public MarkdownBuilder addText(String text) {
+        stringBuilder.append(text);
+        return this;
+    }
+
+    public String build() {
+        return stringBuilder.toString();
+    }
+}
diff --git a/src/main/java/org/apache/process/report_utils/MarkdownLinkBuilder.java b/src/main/java/org/apache/process/report_utils/MarkdownLinkBuilder.java
new file mode 100644
index 0000000..506e32b
--- /dev/null
+++ b/src/main/java/org/apache/process/report_utils/MarkdownLinkBuilder.java
@@ -0,0 +1,26 @@
+package org.apache.process.report_utils;
+
+/**
+ * Created by wangtong.wt on 2017/3/20.
+ *
+ * @author wangtong.wt
+ * @date 2017/03/20
+ */
+public class MarkdownLinkBuilder {
+
+    private StringBuilder sb = new StringBuilder();
+
+    private MarkdownLinkBuilder() {}
+
+    public static MarkdownLinkBuilder builder() {
+        return new MarkdownLinkBuilder();
+    }
+
+    public void setLink(String name, String url) {
+        sb.append("[").append(name).append("]").append("(").append(url).append(")");
+    }
+
+    public String build() {
+        return sb.toString();
+    }
+}
diff --git a/src/main/java/org/apache/process/report_utils/MarkdownTableBuilder.java b/src/main/java/org/apache/process/report_utils/MarkdownTableBuilder.java
new file mode 100644
index 0000000..c9ce58b
--- /dev/null
+++ b/src/main/java/org/apache/process/report_utils/MarkdownTableBuilder.java
@@ -0,0 +1,53 @@
+package org.apache.process.report_utils;
+
+/**
+ * Created by wangtong.wt on 2017/3/20.
+ *
+ * @author wangtong.wt
+ * @date 2017/03/20
+ */
+public class MarkdownTableBuilder {
+    private StringBuilder sb = new StringBuilder();
+    private MarkdownTableBuilder() {}
+
+    public static MarkdownTableBuilder builder() {
+        return new MarkdownTableBuilder();
+    }
+
+    public MarkdownTableBuilder addHead(String... item) {
+        StringBuilder head = new StringBuilder();
+        head.append("|");
+        for (String s : item) {
+            head.append(s).append("|");
+        }
+        head.append("\n");
+
+        head.append("|");
+        for (String s : item) {
+            for (int i = 0; i < s.getBytes().length; i++) {
+                head.append("-");
+            }
+            head.append("|");
+        }
+        head.append("\n");
+
+        sb.append(head);
+        return this;
+    }
+
+    public MarkdownTableBuilder addRow(Object... item) {
+        StringBuilder row = new StringBuilder();
+        row.append("|");
+        for (Object s : item) {
+            row.append(s.toString()).append("|");
+        }
+        row.append("\n");
+
+        sb.append(row);
+        return this;
+    }
+
+    public String build() {
+        return sb.toString();
+    }
+}
diff --git a/src/main/java/org/apache/process/report_utils/RepoFileInfo.java b/src/main/java/org/apache/process/report_utils/RepoFileInfo.java
new file mode 100644
index 0000000..6cbd51f
--- /dev/null
+++ b/src/main/java/org/apache/process/report_utils/RepoFileInfo.java
@@ -0,0 +1,47 @@
+package org.apache.process.report_utils;
+
+public class RepoFileInfo {
+
+    private String suffix;
+    private String fileUrl;
+    private String content;
+
+    private String fileName;
+    public RepoFileInfo() {
+        suffix = null;
+        content = null;
+        fileUrl = null;
+    }
+
+    public String getSuffix() {
+        return suffix;
+    }
+
+    public String getFileUrl() {
+        return fileUrl;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setFileUrl(String fileUrl) {
+        this.fileUrl = fileUrl;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+
+    public void setFileName(String fileName) {
+        this.fileName = fileName;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public void setSuffix(String suffix){
+        this.suffix = suffix;
+    }
+}
diff --git a/src/main/java/org/apache/process/report_utils/testcase/CaseResult.java b/src/main/java/org/apache/process/report_utils/testcase/CaseResult.java
new file mode 100644
index 0000000..4a0a5f2
--- /dev/null
+++ b/src/main/java/org/apache/process/report_utils/testcase/CaseResult.java
@@ -0,0 +1,76 @@
+package org.apache.process.report_utils.testcase;
+
+import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Created by wangtong.wt on 2017/3/20.
+ *
+ * @author wangtong.wt
+ * @date 2017/03/20
+ */
+@Data
+public class CaseResult {
+
+    public static final String CASE_RESULT_SUCCESS = "success";
+
+    public static final String CASE_RESULT_FAILURE = "failure";
+
+    public static final String CASE_RESULT_ERROR = "error";
+
+    public static final String CASE_RESULT_SKIPPED = "skipped";
+
+    private String className;
+
+    private String methodName;
+
+    private String caseDisplayName;
+
+    private String result;
+
+    private double time;
+
+    private String simpleClassName;
+
+    private String detailInfo;
+
+    private String sysoutLog;
+
+    public CaseResult(String className, String methodName, double time, String result, String detailInfo) {
+        this(className, methodName, null, time, result, detailInfo);
+    }
+
+    public CaseResult(String className, String methodName, String caseDisplayName, double time, String result,
+                      String detailInfo) {
+        this.className = className;
+        this.methodName = filterContent(methodName);
+        this.time = time;
+        this.result = result;
+        this.detailInfo = filterContent(detailInfo);
+        this.simpleClassName = StringUtils.substring(className, StringUtils.lastIndexOf(className, ".") + 1);
+        this.caseDisplayName = caseDisplayName;
+    }
+
+    public CaseResult(String className, String methodName, double time, String result, String detailInfo,
+                      String sysoutLog) {
+        this(className, methodName, time, result, detailInfo);
+        this.sysoutLog = sysoutLog;
+    }
+
+    public CaseResult(String className, String methodName, String caseDisplayName, double time, String result,
+                      String detailInfo,
+                      String sysoutLog) {
+        this(className, methodName, time, result, detailInfo, sysoutLog);
+        this.caseDisplayName = caseDisplayName;
+    }
+
+    private String filterContent(String content) {
+        if (StringUtils.isBlank(content)) {
+            return content;
+        }
+        if (StringUtils.length(content) > 10000) {
+            content = StringUtils.substring(content, 0, 10000);
+        }
+        return content.replace("*+TDDL", "++TDDL").replace("!+TDDL", "++TDDL").replace("*TDDL:", "++TDDL");
+    }
+}
diff --git a/src/main/java/org/apache/process/report_utils/testcase/TaskResult.java b/src/main/java/org/apache/process/report_utils/testcase/TaskResult.java
new file mode 100644
index 0000000..a920c72
--- /dev/null
+++ b/src/main/java/org/apache/process/report_utils/testcase/TaskResult.java
@@ -0,0 +1,232 @@
+package org.apache.process.report_utils.testcase;
+
+import org.apache.process.config.Configs;
+import org.apache.process.report_utils.*;
+import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Created by wangtong.wt on 2017/3/20.
+ *
+ * @author wangtong.wt
+ * @author wuyfee.
+ * @date 2017/03/20
+ * @date 2023/07/28
+ */
+@Data
+public class TaskResult {
+
+    /**
+     * case run time.
+     */
+    protected double costTime;
+
+    private Map<String, CaseResult> successCaseMap = new HashMap<>();
+    private Map<String, CaseResult> failureCaseMap = new HashMap<>();
+    private Map<String, CaseResult> errorCaseMap = new HashMap<>();
+    private Map<String, CaseResult> skipCaseMap = new HashMap<>();
+    private Map<String, String> caseTypeMap = new HashMap<>();
+
+    public TaskResult() {
+    }
+
+    public void addCostTime(double time) {
+        this.costTime = this.costTime + time;
+    }
+
+    public void addCase(CaseResult caseResult) {
+        String type = caseResult.getResult();
+        String caseName = caseResult.getClassName() + "#" + caseResult.getMethodName();
+
+        if (!(StringUtils.equals(type, CaseResult.CASE_RESULT_SUCCESS)
+                || StringUtils.equals(type, CaseResult.CASE_RESULT_FAILURE)
+                || StringUtils.equals(type, CaseResult.CASE_RESULT_ERROR)
+                || StringUtils.equals(type, CaseResult.CASE_RESULT_SKIPPED))) {
+            return;
+        }
+
+        // Determine whether the historically running Case includes the current running Case
+        // If it is included, judge whether the historical running Case is successful or failed. If it succeeds, ignore it. If it fails, it will overwrite it.
+        if (caseTypeMap.containsKey(caseName)) {
+            if (StringUtils.equals(caseTypeMap.get(caseName), CaseResult.CASE_RESULT_SUCCESS)) {
+                return;
+            }
+            if (StringUtils.equals(caseTypeMap.get(caseName), CaseResult.CASE_RESULT_FAILURE)) {
+                if (StringUtils.equals(type, CaseResult.CASE_RESULT_SUCCESS)) {
+                    failureCaseMap.remove(caseName);
+                    successCaseMap.put(caseName, caseResult);
+                } else if (StringUtils.equals(type, CaseResult.CASE_RESULT_FAILURE)) {
+                    failureCaseMap.put(caseName, caseResult);
+                } else if (StringUtils.equals(type, CaseResult.CASE_RESULT_ERROR)) {
+                    failureCaseMap.remove(caseName);
+                    errorCaseMap.put(caseName, caseResult);
+                }
+            } else if (StringUtils.equals(caseTypeMap.get(caseName), CaseResult.CASE_RESULT_ERROR)) {
+                if (StringUtils.equals(type, CaseResult.CASE_RESULT_SUCCESS)) {
+                    errorCaseMap.remove(caseName);
+                    successCaseMap.put(caseName, caseResult);
+                } else if (StringUtils.equals(type, CaseResult.CASE_RESULT_FAILURE)) {
+                    errorCaseMap.remove(caseName);
+                    failureCaseMap.put(caseName, caseResult);
+                } else if (StringUtils.equals(type, CaseResult.CASE_RESULT_ERROR)) {
+                    errorCaseMap.put(caseName, caseResult);
+                }
+            }
+            caseTypeMap.put(caseName, type);
+        } else {
+            if (StringUtils.equals(type, CaseResult.CASE_RESULT_SUCCESS)) {
+                successCaseMap.put(caseName, caseResult);
+            } else if (StringUtils.equals(type, CaseResult.CASE_RESULT_FAILURE)) {
+                failureCaseMap.put(caseName, caseResult);
+            } else if (StringUtils.equals(type, CaseResult.CASE_RESULT_ERROR)) {
+                errorCaseMap.put(caseName, caseResult);
+            } else if (StringUtils.equals(type, CaseResult.CASE_RESULT_SKIPPED)) {
+                skipCaseMap.put(caseName, caseResult);
+            }
+            caseTypeMap.put(caseName, type);
+        }
+    }
+
+    /**
+     * @param repoName    repository name.
+     * @param repoBaseUrl test-code base url.
+     * @param gitBranch   repository beanch.
+     * @param codePath    test-code poth in repository.
+     * @param githubToken GitHub access token.
+     * @return markdown content.
+     * @throws IOException exception.
+     */
+    public String toMarkdown(String repoName, String repoBaseUrl, String gitBranch, String codePath, String githubToken) throws IOException {
+        MarkdownBuilder builder = MarkdownBuilder.builder();
+        builder.addHeader("Test Cases Info", 2);
+        MarkdownTableBuilder tableBuilder = MarkdownTableBuilder.builder();
+
+        tableBuilder.addHead("Total", "Success✅", "Failure❌", "Error❎", "Skipped️↪️");
+        tableBuilder.addRow(getTotalCount(), getSuccessCount(), getFailureCount(),
+                getErrorCount(), getSkipCount());
+
+        builder.addTable(tableBuilder);
+
+        GetGithubRepoInfo getGithubRepoInfo = new GetGithubRepoInfo();
+        HashMap<String, RepoFileInfo> fileInfoMap = new HashMap<>();
+
+        String url = GetGithubRepoInfo.API_BASE_URL + "/" + repoName + "/contents/" + codePath;
+        // get all files and their url in repository.
+        getGithubRepoInfo.getAllFilePath(url, gitBranch, githubToken, fileInfoMap);
+        builder.addHeader("🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰", 4);
+        builder.addHeader(":x: Failed/Error Case Detail", 3);
+
+        List<CaseResult> allBadCase = new ArrayList<>(failureCaseMap.values());
+        allBadCase.addAll(errorCaseMap.values());
+
+        if (allBadCase.size() > 0) {
+            Configs.IS_ALL_CASE_SUCCESS = false;
+            //builder.addHeader("⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️️", 4);
+            builder.addHeader(String.format("⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️ %s: Fail/Error ! ⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️", codePath), 4);
+            //builder.addHeader("⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️⚡️", 4);
+        }
+        for (CaseResult badCase : allBadCase) {
+            MarkdownLinkBuilder linkBuilder = MarkdownLinkBuilder.builder();
+
+            String caseUrl = getGithubRepoInfo.getCaseUrl(fileInfoMap, githubToken, getClassName(badCase.getClassName()), badCase.getMethodName(), repoName, gitBranch);
+            if (Objects.equals(caseUrl, "")) {
+                caseUrl = repoBaseUrl;
+            }
+            linkBuilder.setLink(badCase.getClassName() + "." + badCase.getMethodName(), caseUrl);
+
+            builder.addHeader("Name: " + linkBuilder.build() +
+                    " Time: " + badCase.getTime() + "s", 4);
+
+            MarkdownBuilder exceptionBuilder = MarkdownBuilder.builder();
+            exceptionBuilder.newLine();
+            exceptionBuilder.addText("- Exception").newLine();
+            exceptionBuilder.addText("```").newLine();
+            exceptionBuilder.addText(badCase.getDetailInfo()).newLine();
+            exceptionBuilder.addText("```").newLine();
+            exceptionBuilder.addText("- System out info").newLine();
+            exceptionBuilder.addText("```").newLine();
+            exceptionBuilder.addText(badCase.getSysoutLog()).newLine();
+            exceptionBuilder.addText("```").newLine();
+
+            builder.addCollapse("Exception Detail", exceptionBuilder.build());
+        }
+        if (allBadCase.size() == 0) {
+            //builder.addHeader("🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉", 4);
+            builder.addHeader(String.format("🎉🎉🎉🎉🎉🎉🎉🎉 %s: Pass ! 🎉🎉🎉🎉🎉🎉🎉🎉", codePath), 4);
+            //builder.addHeader("🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉", 4);
+        }
+
+        writeContentToMarkdown(repoBaseUrl, builder, successCaseMap, fileInfoMap, ":white_check_mark: Success Cases", 3, "✅");
+        writeContentToMarkdown(repoBaseUrl, builder, skipCaseMap, fileInfoMap, ":next_track_button: Skipped Cases", 3, "↪️");
+        builder.addHeader("🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰", 4);
+        return builder.build();
+    }
+
+    public int getSuccessCount() {
+        return successCaseMap.size();
+    }
+
+    public int getFailureCount() {
+        return failureCaseMap.size();
+    }
+
+    public int getErrorCount() {
+        return errorCaseMap.size();
+    }
+
+    public int getSkipCount() {
+        return skipCaseMap.size();
+    }
+
+    public int getTotalCount() {
+        return caseTypeMap.size();
+    }
+
+    /**
+     * get class name.
+     *
+     * @param name whole name, such as: com.alibaba.nacos.config.ConfigSyncTest.
+     * @return class name. such as: ConfigSyncTest.
+     */
+    public String getClassName(String name) {
+        String[] classNameArray = name.contains(".") ? name.split("\\.") : name.split("/");
+        return classNameArray[classNameArray.length - 1];
+    }
+
+    /**
+     * write case content to markdown
+     *
+     * @param repoBaseUrl repository base url.
+     * @param builder     Markdown builder.
+     * @param caseMap     case result map.
+     * @param fileInfoMap repository file map.
+     * @param title       section title.
+     * @param level       font size level.
+     * @param logo        logo.
+     */
+    public void writeContentToMarkdown(String repoBaseUrl, MarkdownBuilder builder, Map<String, CaseResult> caseMap, HashMap<String, RepoFileInfo> fileInfoMap, String title, int level, String logo) {
+        builder.addHeader("🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰🟰", level + 1);
+        builder.addHeader(title, level);
+
+        MarkdownBuilder builder_tmp = MarkdownBuilder.builder();
+        MarkdownTableBuilder tableBuilder = MarkdownTableBuilder.builder();
+        tableBuilder.addHead("Test Name", "Result", "Time/s");
+
+        for (CaseResult caseResult : caseMap.values()) {
+            MarkdownLinkBuilder linkBuilder = MarkdownLinkBuilder.builder();
+            String caseUrl = repoBaseUrl;
+            if (fileInfoMap.containsKey(getClassName(caseResult.getClassName()))) {
+                caseUrl = fileInfoMap.get(getClassName(caseResult.getClassName())).getFileUrl();
+            }
+            linkBuilder.setLink(caseResult.getClassName() + "." + caseResult.getMethodName(),
+                    caseUrl);
+            tableBuilder.addRow(linkBuilder.build(), logo + "pass", caseResult.getTime());
+        }
+        builder.addCollapse("🔎  Case Detail ", builder_tmp.addTable(tableBuilder).build());
+    }
+
+
+}
diff --git a/src/main/java/org/apache/process/report_utils/testcase/TestResultParser.java b/src/main/java/org/apache/process/report_utils/testcase/TestResultParser.java
new file mode 100644
index 0000000..464f168
--- /dev/null
+++ b/src/main/java/org/apache/process/report_utils/testcase/TestResultParser.java
@@ -0,0 +1,15 @@
+package org.apache.process.report_utils.testcase;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Created by wangtong.wt on 2017/3/20.
+ *
+ * @author wangtong.wt
+ * @date 2017/03/20
+ */
+public interface TestResultParser {
+
+    TaskResult parseTestResult(List<File> files);
+}
diff --git a/src/main/java/org/apache/process/report_utils/testcase/xUnitTestResultParser.java b/src/main/java/org/apache/process/report_utils/testcase/xUnitTestResultParser.java
new file mode 100644
index 0000000..6f86387
--- /dev/null
+++ b/src/main/java/org/apache/process/report_utils/testcase/xUnitTestResultParser.java
@@ -0,0 +1,223 @@
+package org.apache.process.report_utils.testcase;
+
+import org.apache.process.report_utils.FileUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.springframework.util.xml.DomUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Created by wangtong.wt on 2017/3/20.
+ *
+ * @author wangtong.wt
+ * @date 2017/03/20
+ */
+@Slf4j
+public class xUnitTestResultParser implements TestResultParser {
+
+    protected TaskResult taskResult = new TaskResult();
+
+    protected Set<String> parsedFiles = new HashSet<>();
+
+
+    /**
+     * parse the test result by test file.
+     */
+    @Override
+    public TaskResult parseTestResult(List<File> files) {
+        List<File> targetFiles = new ArrayList<>();
+        //过滤测试结果文件
+        files = files.stream().filter(file -> {
+            return file.getName().matches(".*xml");
+        }).collect(Collectors.toList());
+
+        if (CollectionUtils.isEmpty(files)) {
+            return null;
+        }
+
+        for (File file : files) {
+            // since the case of junit5 retrying the error will cover the report, it is judged according to the file md5.
+            String fileMd5 = FileUtils.getFileMd5(file);
+            if (StringUtils.isNotBlank(fileMd5) && !parsedFiles.contains(fileMd5)) {
+                // determine whether the file has ended, and parse it if it has ended.
+                if (StringUtils.contains(getLastLineFromFile(file.getAbsolutePath()), "</testsuites>") ||
+                        StringUtils.contains(getLastLineFromFile(file.getAbsolutePath()), "</testsuite>")) {
+                    parsedFiles.add(fileMd5);
+                    targetFiles.add(file);
+                }
+            }
+        }
+
+        if (CollectionUtils.isNotEmpty(targetFiles)) {
+            parseTaskResult(targetFiles, taskResult);
+        }
+
+        return taskResult;
+
+    }
+
+    protected void parseTaskResult(List<File> files, TaskResult taskResult) {
+        for (File file : files) {
+            parseFileResult(file, taskResult);
+        }
+    }
+
+    // parse a file.
+    private void parseFileResult(File file, TaskResult taskResult) {
+        try {
+            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            DocumentBuilder builder = factory.newDocumentBuilder();
+            Document document = builder.parse(Files.newInputStream(file.toPath()));
+                    Element testSuitesElement = document.getDocumentElement();
+                    List<Element> testSuitElements = DomUtils.getChildElementsByTagName(testSuitesElement, "testsuite");
+                    if (testSuitElements.isEmpty()) {
+                        parseTestClass(testSuitesElement, taskResult);
+                    } else {
+                        for (Element testSuitElement : testSuitElements) {
+                            parseTestClass(testSuitElement, taskResult);
+                        }
+                    }
+        } catch (Exception e) {
+            log.error(String.format("Parsing XML File[%s] failed!", file.getAbsoluteFile()), e);
+        }
+    }
+
+    // parse test class.
+    private void parseTestClass(Element testSuiteElement, TaskResult taskResult) {
+        if (testSuiteElement.hasAttribute("time")) {
+            taskResult.addCostTime(Double.parseDouble(testSuiteElement.getAttribute("time").replace(",", "").trim()));
+        }
+
+        String className;
+        List<Element> testCaseElements = DomUtils.getChildElementsByTagName(testSuiteElement, "testcase");
+
+        if (CollectionUtils.isNotEmpty(testCaseElements)) {
+            for (Element testCaseElement : testCaseElements) {
+                String methodName = testCaseElement.getAttribute("name");
+                // case display name
+                String caseDisplayName = testCaseElement.getAttribute("caseName");
+                className = testCaseElement.getAttribute("classname");
+                double time = Double.parseDouble(testCaseElement.getAttribute("time").replace(",", "").trim());
+
+                if (!parseTestMethod(className, methodName, caseDisplayName, time, CaseResult.CASE_RESULT_FAILURE,
+                    taskResult,
+                    testCaseElement)) {
+                    if (!parseTestMethod(className, methodName, caseDisplayName, time, CaseResult.CASE_RESULT_ERROR,
+                        taskResult,
+                        testCaseElement)) {
+                        if (!parseTestMethod(className, methodName, caseDisplayName, time,
+                            CaseResult.CASE_RESULT_SKIPPED, taskResult,
+                            testCaseElement)) {
+                            parseTestMethod(className, methodName, caseDisplayName, time,
+                                CaseResult.CASE_RESULT_SUCCESS, taskResult,
+                                testCaseElement);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    protected boolean parseTestMethod(String className, String methodName, String caseDisplayName, double time,
+                                    String type,
+                                    TaskResult taskResult, Element testCaseElement) {
+        boolean done = false;
+        if (StringUtils.equals(type, CaseResult.CASE_RESULT_SUCCESS)) {
+            taskResult.addCase(new CaseResult(className, methodName, caseDisplayName, time, type, ""));
+            done = true;
+        } else {
+            Element typedElement = DomUtils.getChildElementByTagName(testCaseElement, type);
+
+            if (StringUtils.isEmpty(testCaseElement.getAttribute("status"))){
+                if (typedElement != null) {
+                    String content = "";
+                    if (StringUtils.equals(type, CaseResult.CASE_RESULT_FAILURE) || StringUtils.equals(type,
+                            CaseResult.CASE_RESULT_ERROR)) {
+                        content = typedElement.getFirstChild().getNodeValue();
+                        String sysoutLog = getSysoutInfo(testCaseElement);
+                        if (StringUtils.length(sysoutLog) > 1024*1024) {
+                            sysoutLog = StringUtils.substring(sysoutLog, 0, 1024*1024) + "\r\n";
+                            sysoutLog += "System-out log over 1024 length, check it in run log......";
+                        }
+                        taskResult.addCase(
+                                new CaseResult(className, methodName, caseDisplayName, time, type, content, sysoutLog));
+                    } else if (StringUtils.equals(type, CaseResult.CASE_RESULT_SKIPPED)) {
+                        taskResult.addCase(new CaseResult(className, methodName, caseDisplayName, time, type, content));
+                    }
+                    done = true;
+                }
+            } else {
+                if (typedElement != null) {
+                    String content = "";
+                    if (StringUtils.equals(type, CaseResult.CASE_RESULT_FAILURE) || StringUtils.equals(type,
+                            CaseResult.CASE_RESULT_ERROR)) {
+                        content = typedElement.getAttribute("message");
+                        String sysoutLog = typedElement.getFirstChild().getNodeValue();
+                        if (StringUtils.length(sysoutLog) > 1024*1024) {
+                            sysoutLog = StringUtils.substring(sysoutLog, 0, 1024*1024) + "\r\n";
+                            sysoutLog += "System-out log over 1024 length, check it in run log......";
+                        }
+                        taskResult.addCase(
+                                new CaseResult(className, methodName, caseDisplayName, time, type, content, sysoutLog));
+                    }
+                    done = true;
+                }
+                if (StringUtils.equals(type, CaseResult.CASE_RESULT_SKIPPED)) {
+                    if (testCaseElement.getAttribute("status").equals("notrun")) {
+                        taskResult.addCase(new CaseResult(className, methodName, caseDisplayName, time, type, ""));
+                        done = true;
+                    }
+                }
+            }
+        }
+        return done;
+    }
+
+    private String getSysoutInfo(Element testcaseElement) {
+        StringBuilder sb = new StringBuilder();
+        try {
+            Element sysoutElement = DomUtils.getChildElementByTagName(testcaseElement, "system-out");
+            if (sysoutElement != null) {
+                String systemOut = new String(sysoutElement.getFirstChild().getNodeValue().getBytes(),
+                    StandardCharsets.UTF_8);
+                sb.append(systemOut).append("\r\n");
+            }
+            Element syserrElement = DomUtils.getChildElementByTagName(testcaseElement, "system-err");
+            if (syserrElement != null) {
+                sb.append(new String(syserrElement.getFirstChild().getNodeValue().getBytes(), StandardCharsets.UTF_8));
+            }
+        } catch (Exception e) {
+            log.error("JunitTestResultParser getSysoutInfo exception:" + ExceptionUtils.getStackTrace(e));
+        }
+        return sb.toString();
+    }
+
+    protected String getLastLineFromFile(String file) {
+        try {
+            List<String> lines = IOUtils.readLines(Files.newInputStream(Paths.get(file)));
+            if (CollectionUtils.isNotEmpty(lines)) {
+                return lines.get(lines.size() - 1);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return "";
+    }
+}
diff --git a/src/main/java/org/apache/process/utils/ConfigUtils.java b/src/main/java/org/apache/process/utils/ConfigUtils.java
new file mode 100644
index 0000000..23d1d10
--- /dev/null
+++ b/src/main/java/org/apache/process/utils/ConfigUtils.java
@@ -0,0 +1,104 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Base64;
+
+@Slf4j
+public class ConfigUtils {
+
+    public String setConfig(String kubeConfig) throws IOException {
+
+        String usrHome = System.getProperty("user.home");
+        String kubeDirPath = String.format("%s/.kube", usrHome);
+        File kubeDir = new File(kubeDirPath);
+        if (!kubeDir.exists() && !kubeDir.mkdirs()) {
+            log.error("{} directory create fail!", kubeDirPath);
+        }
+        String kubeFilePath = String.format("%s/.kube/config", usrHome);
+        File kubeFile = new File(kubeFilePath);
+        if (kubeDir.exists()) {
+            kubeFile.delete();
+        }
+        if (!kubeFile.createNewFile()) {
+            log.error("{} create fail!", kubeFilePath);
+        }
+        try {
+            // 覆盖模式写
+            FileWriter fileWriter = new FileWriter(kubeFilePath);
+            fileWriter.write(kubeConfig);
+            fileWriter.close();
+        } catch (IOException e) {
+            log.error("write {} error! ", kubeFilePath);
+        }
+        return kubeFilePath;
+    }
+
+    /**
+     * get velaUX username and password
+     *
+     * @param kubeConfig ask config
+     * @return username:password
+     */
+    public String getAuthInfoFromConfig(String kubeConfig) {
+        String text = kubeConfig.length() > 150 ? kubeConfig.substring(kubeConfig.length() - 150) : kubeConfig;
+        StringBuilder userName = new StringBuilder();
+        StringBuilder password = new StringBuilder();
+        boolean digitMark = false;
+        for (int index = text.length() - 1; index >= 0; index--) {
+            if (userName.length() >= 6 && password.length() >= 12) {
+                break;
+            }
+            boolean isLetter = Character.isLetter(text.charAt(index));
+            boolean isDigit = Character.isDigit(text.charAt(index));
+            if (isDigit || isLetter) {
+                if (isLetter && userName.length() < 6) {
+                    userName.append(Character.toLowerCase(text.charAt(index)));
+                }
+                if (password.length() < 12) {
+                    if (digitMark && isDigit) {
+                        password.append(text.charAt(index));
+                        digitMark = false;
+                    } else if (!digitMark && isLetter) {
+                        password.append(text.charAt(index));
+                        digitMark = true;
+                    }
+                }
+            }
+        }
+        return userName + ":" + password;
+    }
+
+    /**
+     * decoder ask config.
+     *
+     * @param config ask config.
+     * @return ask config after base64 decoder.
+     */
+    public String base64Decoder(String config) {
+        byte[] base64Bytes = Base64.getDecoder().decode(config.replace("\n", ""));
+        return new String(base64Bytes);
+    }
+}
diff --git a/src/main/java/org/apache/process/utils/GetParam.java b/src/main/java/org/apache/process/utils/GetParam.java
new file mode 100644
index 0000000..cdaa7c7
--- /dev/null
+++ b/src/main/java/org/apache/process/utils/GetParam.java
@@ -0,0 +1,51 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.utils;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.json.JSONObject;
+import org.yaml.snakeyaml.Yaml;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+
+public class GetParam {
+    public HashMap<String, String> setParam(CommandLine cmd) {
+        HashMap<String, String> result = new HashMap<>();
+        for (Option option : cmd.getOptions()) {
+            result.put(option.getLongOpt(), option.getValue());
+        }
+        return result;
+    }
+
+    public static LinkedHashMap<String, Object> yamlToMap(String input) {
+        Yaml yaml = new Yaml();
+        return (LinkedHashMap<String, Object>) yaml.load(input);
+    }
+
+    public static HashMap<String, Object> parseDeployInput(String input, String target) {
+        LinkedHashMap<String, Object> builderMap = yamlToMap(input);
+        LinkedHashMap<String, Object> helmValuesMap = (LinkedHashMap<String, Object>) builderMap.get(target);
+        JSONObject jsonObject = new JSONObject(helmValuesMap);
+        builderMap.put(target, jsonObject.toString().replaceAll("\"", "\\\\\"").toString());
+        return builderMap;
+    }
+}
diff --git a/src/main/java/org/apache/process/utils/PrintInfo.java b/src/main/java/org/apache/process/utils/PrintInfo.java
new file mode 100644
index 0000000..394e069
--- /dev/null
+++ b/src/main/java/org/apache/process/utils/PrintInfo.java
@@ -0,0 +1,45 @@
+/*
+ * #
+ * # Licensed to the Apache Software Foundation (ASF) under one or more
+ * # contributor license agreements.  See the NOTICE file distributed with
+ * # this work for additional information regarding copyright ownership.
+ * # The ASF licenses this file to You 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 org.apache.process.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.Response;
+
+import java.io.IOException;
+
+@Slf4j
+public class PrintInfo {
+
+    public static boolean printRocketInfo(Response response, String message) throws IOException {
+        if (response.isSuccessful()) {
+            log.info(message);
+            return true;
+        } else {
+            log.error("Response fail! Message: {}", response.body().string());
+            return true;
+        }
+    }
+
+    public static boolean isResponseSuccess(Response response) {
+        boolean isSuccessed = response.isSuccessful();
+        response.close();
+        return isSuccessed;
+    }
+}
diff --git a/src/main/resources/logback-test.xml b/src/main/resources/logback-test.xml
new file mode 100644
index 0000000..519d0a6
--- /dev/null
+++ b/src/main/resources/logback-test.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You 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.
+  -->
+
+<configuration debug="false" scan="true" scanPeriod="2 seconds">
+
+    <contextName>logback</contextName>
+    <!-- 输出到控制台 -->
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%logger{12}#%M:%L] - %msg%n</pattern>
+<!--            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%-30.30logger{12}#%-20.20M:%4L] %msg%n</pattern>-->
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <root level="info">
+        <appender-ref ref="CONSOLE" />
+    </root>
+
+</configuration>
diff --git a/src/test/java/org/apache/process/GetGithubRepoInfoTest.java b/src/test/java/org/apache/process/GetGithubRepoInfoTest.java
new file mode 100644
index 0000000..ec56d52
--- /dev/null
+++ b/src/test/java/org/apache/process/GetGithubRepoInfoTest.java
@@ -0,0 +1,103 @@
+package org.apache.process;
+
+import org.apache.process.report_utils.GetGithubRepoInfo;
+import org.apache.process.report_utils.RepoFileInfo;
+import org.apache.process.utils.ConfigUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+public class GetGithubRepoInfoTest {
+
+    @Test
+    public void testGetAllFilePath() {
+        GetGithubRepoInfo client = new GetGithubRepoInfo();
+        HashMap<String, RepoFileInfo> map = new HashMap<>();
+        String url = "https://api.github.com/repos/apache/rocketmq-e2e/contents/java/e2e";
+        String gitBranch = "master";
+        String githubToken = "";
+
+        client.getAllFilePath(url, gitBranch, githubToken, map);
+        for (String key : map.keySet()) {
+            System.out.println(key + ": " + map.get(key).getFileUrl());
+        }
+        for (String key : map.keySet()) {
+            System.out.println(key + ": " + map.get(key).getFileName());
+        }
+        for (String key : map.keySet()) {
+            System.out.println(key + ": " + map.get(key).getSuffix());
+        }
+    }
+
+    @Test
+    public void testGetFunctionRowFromFile() throws IOException {
+        GetGithubRepoInfo client = new GetGithubRepoInfo();
+//        String url = "https://api.github.com/repos/apache/rocketmq-e2e/contents/csharp/rocketmq-client-csharp-tests/test/BaseTest.cs";
+//        String keyword = "BaseTest";
+        String className = "FifoMsgTest";
+        String url = "https://api.github.com/repos/apache/rocketmq-e2e/contents/csharp/rocketmq-client-csharp-tests/test/FifoMsgTest.cs";
+        String keyword = "TestSendFifoMsgSyncSimpleConsumerRecv";
+        String githubToken = "";
+        HashMap<String, RepoFileInfo> fileInfoMap = new HashMap<>();
+        RepoFileInfo repoFileInfo = new RepoFileInfo();
+        repoFileInfo.setFileUrl(url);
+        repoFileInfo.setFileName("FifoMsgTest.cs");
+        fileInfoMap.put(className, repoFileInfo);
+        Assert.assertEquals(client.getFunctionRowFromFile(className, url, keyword, githubToken, fileInfoMap), 40);
+    }
+
+    @Test
+    public void testGetCaseUrl() throws IOException, InterruptedException {
+        GetGithubRepoInfo client = new GetGithubRepoInfo();
+        HashMap<String, RepoFileInfo> golangMap = new HashMap<>();
+
+        String gitBranch = "master";
+        String repo = "apache/rocketmq-e2e";
+        String githubToken = "";
+
+        // go test
+        String url = "https://api.github.com/repos/apache/rocketmq-e2e/contents/golang";
+        client.getAllFilePath(url, gitBranch, githubToken, golangMap);
+
+        String completeUrlGo = client.getCaseUrl(golangMap, githubToken, "delay", "TestDelayMsgAsync", repo, gitBranch);
+        Assert.assertEquals(completeUrlGo, "https://github.com/apache/rocketmq-e2e/blob/master/golang/mqgotest/delay/delaymsg_test.go#L77");
+        Thread.sleep(20000);
+
+        // csharp test
+        HashMap<String, RepoFileInfo> csharpMap = new HashMap<>();
+        String csharpUrl = "https://api.github.com/repos/apache/rocketmq-e2e/contents/csharp";
+        client.getAllFilePath(csharpUrl, gitBranch, githubToken, csharpMap);
+        String completeUrlCsharp = client.getCaseUrl(csharpMap, githubToken, "FifoMsgTest.cs", "TestSendFifoMsgSyncSimpleConsumerRecv", repo, gitBranch);
+        Assert.assertEquals(completeUrlCsharp, "https://github.com/apache/rocketmq-e2e/blob/master/csharp/rocketmq-client-csharp-tests/test/FifoMsgTest.cs#L40");
+        Thread.sleep(60000);
+
+        // java test
+        HashMap<String, RepoFileInfo> javaMap = new HashMap<>();
+        String javaUrl = "https://api.github.com/repos/apache/rocketmq-e2e/contents/java/e2e";
+        client.getAllFilePath(javaUrl, gitBranch, githubToken, javaMap);
+        String completeUrlJava = client.getCaseUrl(javaMap, githubToken, "ConsumerGroupTest.java", "testSystemInnerConsumerGroup", repo, gitBranch);
+        Assert.assertEquals(completeUrlJava, "https://github.com/apache/rocketmq-e2e/blob/master/java/e2e/src/test/java/org/apache/rocketmq/broker/client/consumer/ConsumerGroupTest.java#L65");
+
+    }
+
+    @Test
+    public void testGetUsernameFromConfig() {
+        ConfigUtils configUtils = new ConfigUtils();
+        String config = "wdjweiygduyegdfi3grqwqw8343y9f73yf7wdfwe93gf93f0fu0wwqeqwefveoirvh3984hv934hv348yv3489yvh34urfhc9347yfh394hv==";
+        String result[] = configUtils.getAuthInfoFromConfig(config).split(":");
+        Assert.assertEquals("vhhfyc", result[0]);
+        Assert.assertEquals("v4h7c4h9v8v4", result[1]);
+
+        String config1 = "3dqwdqwdg23dqwdqwdqwfg2qwdqeq38fg23ytyf238Efg238fKt32L87fds3sdA8fB32Cf7X3tA==";
+        String result1[] = configUtils.getAuthInfoFromConfig(config1).split(":");
+        Assert.assertEquals("atxfcb", result1[0]);
+        Assert.assertEquals("A3X7f2B8A3s7", result1[1]);
+
+        String config2 = "3gqewqeqweqewqe23dfg2qweqweq38eqwfg23ytyf238Efg238fKt32L87fds3sdA8fB32Cf7X3tA5==";
+        String result2[] = configUtils.getAuthInfoFromConfig(config2).split(":");
+        Assert.assertEquals("atxfcb", result2[0]);
+        Assert.assertEquals("A3X7f2B8A3s7", result2[1]);
+    }
+}
diff --git a/src/test/java/org/apache/process/action/DeployTest.java b/src/test/java/org/apache/process/action/DeployTest.java
new file mode 100644
index 0000000..6e51664
--- /dev/null
+++ b/src/test/java/org/apache/process/action/DeployTest.java
@@ -0,0 +1,162 @@
+package org.apache.process.action;
+
+import org.apache.process.config.Configs;
+import org.apache.process.utils.ConfigUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.HashMap;
+
+import static org.apache.process.utils.GetParam.parseDeployInput;
+
+public class DeployTest {
+    @Test
+    public void testNacosClusterStartDeploy() throws IOException, InterruptedException {
+        String input = "action: deploy\n" +
+                "ask-config: \"${{ secrets.ASK_CONFIG_VIRGINA }}\"\n" +
+                "waitTimes: 1200\n" +
+                "namespace: nacos-6196355040-3\n" +
+                "velaAppDescription: nacos-push_ci-123456@abcdefghij\n" +
+                "repoName: nacos\n" +
+                "helm:\n" +
+                "  chart: ./cicd/helm\n" +
+                "  git:\n" +
+                "    branch: main\n" +
+                "  repoType: git\n" +
+                "  retries: 3\n" +
+                "  url: https://github.com/nacos-group/nacos-e2e.git\n" +
+                "  values:\n" +
+                "    namespace: nacos-6196355040-3\n" +
+                "    global:\n" +
+                "      mode: cluster\n" +
+                "    nacos:\n" +
+                "      replicaCount: 3\n" +
+                "      image:\n" +
+                "        repository: wuyfeedocker/nacos-ci\n" +
+                "        tag: develop-cee62800-0cb5-478f-9e42-aeb1124db716-8\n" +
+                "      storage:\n" +
+                "        type: mysql\n" +
+                "        db:\n" +
+                "          port: 3306\n" +
+                "          username: nacos\n" +
+                "          password: nacos\n" +
+                "          param: characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false\n" +
+                "    service:\n" +
+                "      nodePort: 30015\n" +
+                "      type: ClusterIP";
+        ;
+        // read config
+        String usrHome = System.getProperty("user.home");
+        String fileName = String.format("%s/.kube/config", usrHome);
+        String config = new String(Files.readAllBytes(Paths.get(fileName)));
+        System.out.println(input);
+
+        // set vela username and password
+        String[] authInfo = new ConfigUtils().getAuthInfoFromConfig(config).split(":");
+        Configs.VELAUX_USERNAME = authInfo[0];
+        Configs.VELAUX_PASSWORD = authInfo[1];
+
+        //new PortForward().startPortForward(Configs.VELA_NAMESPACE, Configs.VELA_POD_LABELS, Configs.PORT_FROWARD);
+        Deploy deploy = new Deploy();
+        HashMap<String, Object> deployMap = parseDeployInput(input, "helm");
+        Assert.assertTrue(deploy.startDeploy(deployMap));
+    }
+
+    @Test
+    public void testNacosStandaloneStartDeploy() throws InterruptedException, IOException {
+        String input = "action: deploy\n" +
+                "ask-config: \"${{ secrets.ASK_CONFIG_VIRGINA }}\"\n" +
+                "waitTimes: 1200\n" +
+                "namespace: nacos-6196355040-0\n" +
+                "velaAppDescription: nacos-push_ci-123456@abcdefghij\n" +
+                "repoName: nacos\n" +
+                "helm:\n" +
+                "  chart: ./cicd/helm\n" +
+                "  git:\n" +
+                "    branch: main\n" +
+                "  repoType: git\n" +
+                "  retries: 3\n" +
+                "  url: https://github.com/nacos-group/nacos-e2e.git\n" +
+                "  values:\n" +
+                "    namespace: nacos-6196355040-0\n" +
+                "    global:\n" +
+                "      mode: standalone\n" +
+                "    nacos:\n" +
+                "      replicaCount: 1\n" +
+                "      image:\n" +
+                "        repository: wuyfeedocker/nacos-ci\n" +
+                "        tag: develop-cee62800-0cb5-478f-9e42-aeb1124db716-8\n" +
+                "      storage:\n" +
+                "        type: embedded\n" +
+                "        db:\n" +
+                "          port: 3306\n" +
+                "          username: nacos\n" +
+                "          password: nacos\n" +
+                "          param: characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false\n" +
+                "    service:\n" +
+                "      nodePort: 30003\n" +
+                "      type: ClusterIP";
+        String usrHome = System.getProperty("user.home");
+        String fileName = String.format("%s/.kube/config", usrHome);
+        String config = new String(Files.readAllBytes(Paths.get(fileName)));
+
+        String authentificationInfo[] = new ConfigUtils().getAuthInfoFromConfig(config).split(":");
+        Configs.VELAUX_USERNAME = authentificationInfo[0];
+        Configs.VELAUX_PASSWORD = authentificationInfo[1];
+
+        //new PortForward().startPortForward(Configs.VELA_NAMESPACE, Configs.VELA_POD_LABELS, Configs.PORT_FROWARD);
+        Deploy deploy = new Deploy();
+        HashMap<String, Object> deployMap = parseDeployInput(input, "helm");
+        Assert.assertTrue(deploy.startDeploy(deployMap));
+
+    }
+
+    @Test
+    public void testRocketmqStartDeploy() throws InterruptedException, IOException {
+        String input = "action: deploy\n" +
+                "ask-config: \"${{ secrets.ASK_CONFIG_VIRGINA }}\"\n" +
+                "waitTimes: 1200\n" +
+                "namespace: rocketmq-12345562-3\n" +
+                "velaAppDescription: rocketmq-push_ci-123456@abcdefghij\n" +
+                "repoName: rocketmq\n" +
+                "helm:\n" +
+                "  chart: ./rocketmq-k8s-helm\n" +
+                "  git:\n" +
+                "    branch: master\n" +
+                "  repoType: git\n" +
+                "  retries: 3\n" +
+                "  url: https://ghproxy.com/https://github.com/apache/rocketmq-docker.git\n" +
+                "  values:\n" +
+                "    nameserver:\n" +
+                "      image:\n" +
+                "        repository: wangtong719/ci-test\n" +
+                "        tag: test2-fd2c5471-bc7a-4388-a6d5-41501e29cfa8-ubuntu\n" +
+                "    broker:\n" +
+                "      image:\n" +
+                "        repository: wangtong719/ci-test\n" +
+                "        tag: test2-fd2c5471-bc7a-4388-a6d5-41501e29cfa8-ubuntu\n" +
+                "    proxy:\n" +
+                "      image:\n" +
+                "        repository: wangtong719/ci-test\n" +
+                "        tag: test2-fd2c5471-bc7a-4388-a6d5-41501e29cfa8-ubuntu\n";
+
+        String usrHome = System.getProperty("user.home");
+        String fileName = String.format("%s/.kube/config", usrHome);
+        String config = new String(Files.readAllBytes(Paths.get(fileName)));
+
+        ConfigUtils configUtils = new ConfigUtils();
+        String[] authentificationInfo = configUtils.getAuthInfoFromConfig(config).split(":");
+        Configs.VELAUX_USERNAME = authentificationInfo[0];
+        Configs.VELAUX_PASSWORD = authentificationInfo[1];
+
+        new PortForward().startPortForward(Configs.VELA_NAMESPACE, Configs.VELA_POD_LABELS, Configs.PORT_FROWARD);
+        Deploy deploy = new Deploy();
+        HashMap<String, Object> deployMap = parseDeployInput(input, "helm");
+        Assert.assertTrue(deploy.startDeploy(deployMap));
+
+    }
+
+}
diff --git a/src/test/java/org/apache/process/action/ProjectCleanTest.java b/src/test/java/org/apache/process/action/ProjectCleanTest.java
new file mode 100644
index 0000000..9659ce7
--- /dev/null
+++ b/src/test/java/org/apache/process/action/ProjectCleanTest.java
@@ -0,0 +1,55 @@
+package org.apache.process.action;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.process.config.Configs;
+import org.apache.process.utils.ConfigUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.LinkedHashMap;
+
+import static org.apache.process.utils.GetParam.yamlToMap;
+
+@Slf4j
+public class ProjectCleanTest {
+    @Test
+    public void testNacosClean() throws IOException, InterruptedException {
+        String input = "action: clean\n" +
+                "namespace: nacos-6196355040-2\n" +
+                "ask-config: ${{ secrets.ASK_CONFIG_VIRGINA }}\n";
+        String usrHome = System.getProperty("user.home");
+        String fileName = String.format("%s/.kube/config", usrHome);
+        String config = new String(Files.readAllBytes(Paths.get(fileName)));
+
+        String[] authentificationInfo = new ConfigUtils().getAuthInfoFromConfig(config).split(":");
+        Configs.VELAUX_USERNAME = authentificationInfo[0];
+        Configs.VELAUX_PASSWORD = authentificationInfo[1];
+
+        //new PortForward().startPortForward(Configs.VELA_NAMESPACE, Configs.VELA_POD_LABELS, Configs.PORT_FROWARD);
+        LinkedHashMap<String, Object> inputMap = yamlToMap(input);
+        EnvClean envClean = new EnvClean();
+        Assert.assertTrue(envClean.clean(inputMap));
+    }
+
+    @Test
+    public void testRocketmqClean() throws IOException, InterruptedException {
+        String input = "action: clean\n" +
+                "namespace: rocketmq-12345562-23315\n" +
+                "ask-config: ${{ secrets.ASK_CONFIG_VIRGINA }}\n";
+        String usrHome = System.getProperty("user.home");
+        String fileName = String.format("%s/.kube/config", usrHome);
+        String config = new String(Files.readAllBytes(Paths.get(fileName)));
+
+        String[] authentificationInfo = new ConfigUtils().getAuthInfoFromConfig(config).split(":");
+        Configs.VELAUX_USERNAME = authentificationInfo[0];
+        Configs.VELAUX_PASSWORD = authentificationInfo[1];
+
+        new PortForward().startPortForward(Configs.VELA_NAMESPACE, Configs.VELA_POD_LABELS, Configs.PORT_FROWARD);
+        LinkedHashMap<String, Object> inputMap = yamlToMap(input);
+        EnvClean envClean = new EnvClean();
+        Assert.assertTrue(envClean.clean(inputMap));
+    }
+}
diff --git a/src/test/java/org/apache/process/action/RepoTestTest.java b/src/test/java/org/apache/process/action/RepoTestTest.java
new file mode 100644
index 0000000..52360ae
--- /dev/null
+++ b/src/test/java/org/apache/process/action/RepoTestTest.java
@@ -0,0 +1,326 @@
+package org.apache.process.action;
+
+import org.apache.process.config.Configs;;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+
+import static org.apache.process.utils.GetParam.yamlToMap;
+
+public class RepoTestTest {
+    @Test
+    public void testNacosRunJavaTest() throws IOException, InterruptedException {
+        String input = "action: test\n" +
+                "namespace: nacos-6196355040-3\n" +
+                "ask-config: ${{ secrets.ASK_CONFIG_VIRGINA }}\n" +
+                "API_VERSION: v1\n" +
+                "KIND: Pod\n" +
+                "RESTART_POLICY: \"Never\" \n" +
+                "ENV:\n" +
+                "  WAIT_TIME: 600\n" +
+                "  REPO_NAME: nacos-grou/nacos-e2e\n" +
+                "  CODE: https://github.com/nacos-group/nacos-e2e\n" +
+                "  BRANCH: main\n" +
+                "  CODE_PATH: java/nacos-2X\n" +
+                "  CMD: mvn clean test -B\n" +
+                "  ALL_IP: null\n" +
+                "CONTAINER:\n" +
+                "  IMAGE: cloudnativeofalibabacloud/test-runner:v0.0.4\n" +
+                "  RESOURCE_LIMITS:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi\n" +
+                "  RESOURCE_REQUIRE:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi";
+
+
+        //new PortForward().startPortForward(Configs.VELA_NAMESPACE, Configs.VELA_POD_LABELS, Configs.PORT_FROWARD);
+        LinkedHashMap<String, Object> inputMap = yamlToMap(input);
+        RepoTest project = new RepoTest();
+        project.runTest(inputMap);
+
+        LinkedHashMap<String, Object> inputMap1 = yamlToMap(input);
+        GenerateReport generateReport = new GenerateReport();
+        Assert.assertTrue(generateReport.generateReportMarkDown(inputMap1));
+
+    }
+
+    @Test
+    public void testNacosRunGoTest() throws IOException, InterruptedException {
+        String input = "action: test\n" +
+                "namespace: nacos-6196355040-3\n" +
+                "ask-config: ${{ secrets.ASK_CONFIG_VIRGINA }}\n" +
+                "API_VERSION: v1\n" +
+                "KIND: Pod\n" +
+                "RESTART_POLICY: \"Never\" \n" +
+                "ENV:\n" +
+                "  WAIT_TIME: 600\n" +
+                "  REPO_NAME: nacos-group/nacos-e2e\n" +
+                "  CODE: https://github.com/nacos-group/nacos-e2e\n" +
+                "  BRANCH: main\n" +
+                "  CODE_PATH: golang\n" +
+                "  CMD: |\n" +
+                "    cd /root/code/golang\n" +
+                "    go mod init nacos_go_test\n" +
+                "    go mod tidy\n" +
+                "    gotestsum --junitfile ./target/surefire-reports/TEST-report.xml ./nacosgotest\n" + //-timeout 2m  -v
+                "  ALL_IP: null\n" +
+                "CONTAINER:\n" +
+                "  IMAGE: cloudnativeofalibabacloud/test-runner:v0.0.4\n" +
+                "  RESOURCE_LIMITS:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi\n" +
+                "  RESOURCE_REQUIRE:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi";
+
+        //new PortForward().startPortForward(Configs.VELA_NAMESPACE, Configs.VELA_POD_LABELS, Configs.PORT_FROWARD);
+        LinkedHashMap<String, Object> inputMap = yamlToMap(input);
+
+        RepoTest project = new RepoTest();
+        project.runTest(inputMap);
+
+        LinkedHashMap<String, Object> inputMap1 = yamlToMap(input);
+        GenerateReport generateReport = new GenerateReport();
+        Assert.assertTrue(generateReport.generateReportMarkDown(inputMap1));
+
+    }
+
+    @Test
+    public void testNacosRunCsharpTest() throws IOException, InterruptedException {
+        // nacos-group/nacos-e2e  main
+        String input = "action: test\n" +
+                "namespace: nacos-6196355040-3\n" +
+                "ask-config: ${{ secrets.ASK_CONFIG_VIRGINA }}\n" +
+                "API_VERSION: v1\n" +
+                "KIND: Pod\n" +
+                "RESTART_POLICY: \"Never\" \n" +
+                "ENV:\n" +
+                "  REPO_NAME: Wuyunfan-BUPT/nacos-e2e\n" +
+                "  CODE: https://github.com/Wuyunfan-BUPT/nacos-e2e\n" +
+                "  BRANCH: yf-dev\n" +
+                "  CODE_PATH: csharp\n" +
+                "  CMD: |\n" +
+                "    rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm \n" +
+                "    yum -y install dotnet-sdk-3.1 \n" +
+                "    yum -y install aspnetcore-runtime-7.0 \n" +
+                "    cd /root/code/csharp/nacos-csharp-sdk-test \n" +
+                "    dotnet restore\n" +
+                "    dotnet test --logger:\"junit;LogFilePath=../target/surefire-reports/TEST-result.xml\" \n" +
+                "  ALL_IP: null\n" +
+                "CONTAINER:\n" +
+                "  IMAGE: cloudnativeofalibabacloud/test-runner:v0.0.4\n" +
+                "  RESOURCE_LIMITS:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi\n" +
+                "  RESOURCE_REQUIRE:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi";
+        //new PortForward().startPortForward(Configs.VELA_NAMESPACE, Configs.VELA_POD_LABELS, Configs.PORT_FROWARD);
+        LinkedHashMap<String, Object> inputMap = yamlToMap(input);
+        RepoTest project = new RepoTest();
+        project.runTest(inputMap);
+
+        LinkedHashMap<String, Object> inputMap1 = yamlToMap(input);
+        GenerateReport generateReport = new GenerateReport();
+        Assert.assertTrue(generateReport.generateReportMarkDown(inputMap1));
+
+    }
+
+    @Test
+    public void testNacosRunCppTest() throws IOException, InterruptedException {
+        String input = "action: test\n" +
+                "namespace: nacos-6196355040-4\n" +
+                "ask-config: ${{ secrets.ASK_CONFIG_VIRGINA }}\n" +
+                "API_VERSION: v1\n" +
+                "KIND: Pod\n" +
+                "RESTART_POLICY: \"Never\" \n" +
+                "ENV:\n" +
+                "  REPO_NAME: Wuyunfan-BUPT/nacos-e2e\n" +
+                "  CODE: https://github.com/Wuyunfan-BUPT/nacos-e2e\n" +
+                "  BRANCH: yf-dev\n" +
+                "  CODE_PATH: cpp\n" +
+                "  CMD: |\n" +
+                "    cd /root/code/cpp && make install\n " +
+                "    echo \"export LD_LIBRARY_PATH=/usr/local/lib\" >> ~/.bashrc  && source ~/.bashrc\n" +
+                "    cd /root/code/cpp/nacoscpptest \n" +
+                "    g++ nacos_test.cpp -o nacos_test -lgtest -lpthread -I/usr/local/include/nacos/ -L/usr/local/lib/  -lnacos-cli \n" +
+                "    chmod 777 nacos_test && ./nacos_test --gtest_output=\"xml:../target/surefire-reports/TEST-gtestresults.xml\" \n" +
+                "  ALL_IP: null\n" +
+                "CONTAINER:\n" +
+                "  IMAGE: cloudnativeofalibabacloud/test-runner:v0.0.4\n" +
+                "  RESOURCE_LIMITS:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi\n" +
+                "  RESOURCE_REQUIRE:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi";
+
+        //new PortForward().startPortForward(Configs.VELA_NAMESPACE, Configs.VELA_POD_LABELS, Configs.PORT_FROWARD);
+        LinkedHashMap<String, Object> inputMap = yamlToMap(input);
+        RepoTest project = new RepoTest();
+        project.runTest(inputMap);
+
+        LinkedHashMap<String, Object> inputMap1 = yamlToMap(input);
+        GenerateReport generateReport = new GenerateReport();
+        Assert.assertTrue(generateReport.generateReportMarkDown(inputMap1));
+    }
+
+
+    @Test
+    public void testNacosRunPythonTest() throws IOException, InterruptedException {
+        String input = "action: test\n" +
+                "namespace: nacos-6196355040-1\n" +
+                "ask-config: ${{ secrets.ASK_CONFIG_VIRGINA }}\n" +
+                "API_VERSION: v1\n" +
+                "KIND: Pod\n" +
+                "RESTART_POLICY: \"Never\" \n" +
+                "ENV:\n" +
+                "  WAIT_TIME: 3000\n" +
+                "  REPO_NAME: nacos-group/nacos-e2e\n" +
+                "  CODE: https://github.com/nacos-group/nacos-e2e\n" +
+                "  BRANCH: main\n" +
+                "  CODE_PATH: python\n" +
+                "  CMD: |\n" +
+                "    cd /root/code/python \n" +
+                "    pip3 install -r requirements.txt \n" +
+                "    source ~/.bashrc \n" +
+                "    cd nacospythontest && pytest --junitxml ../target/surefire-reports/TEST-report.xml test/*_test.py  --log-cli-level=DEBUG\n" +//
+//                "    sleep 30000\n" +
+                "  ALL_IP: null\n" +
+                "CONTAINER:\n" +
+                "  IMAGE: cloudnativeofalibabacloud/test-runner:v0.0.4\n" +
+                "  RESOURCE_LIMITS:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi\n" +
+                "  RESOURCE_REQUIRE:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi";
+
+        //new PortForward().startPortForward(Configs.VELA_NAMESPACE, Configs.VELA_POD_LABELS, Configs.PORT_FROWARD);
+        LinkedHashMap<String, Object> inputMap = yamlToMap(input);
+        RepoTest project = new RepoTest();
+        project.runTest(inputMap);
+
+        LinkedHashMap<String, Object> inputMap1 = yamlToMap(input);
+        GenerateReport generateReport = new GenerateReport();
+        Assert.assertTrue(generateReport.generateReportMarkDown(inputMap1));
+    }
+
+    @Test
+    public void testNacosRunNodejsTest() throws IOException, InterruptedException {
+        String input = "action: test\n" +
+                "namespace: nacos-6196355040-4\n" +
+                "ask-config: ${{ secrets.ASK_CONFIG_VIRGINA }}\n" +
+                "API_VERSION: v1\n" +
+                "KIND: Pod\n" +
+                "RESTART_POLICY: \"Never\" \n" +
+                "ENV:\n" +
+                "  WAIT_TIME: 600\n" +
+                "  REPO_NAME: Wuyunfan-BUPT/nacos-e2e\n" +
+                "  CODE: https://github.com/Wuyunfan-BUPT/nacos-e2e\n" +
+                "  BRANCH: yf-dev\n" +
+                "  CODE_PATH: nodejs\n" +
+                "  CMD: |\n" +
+                "    cd /root/code/nodejs/nacosnodejstest \n" +
+                "    npm install \n" +
+                "    mocha test --reporter mocha-junit-reporter --reporter-options mochaFile=../target/surefire-reports/TEST-report.xml \n" +
+                "  ALL_IP: null\n" +
+                "CONTAINER:\n" +
+                "  IMAGE: cloudnativeofalibabacloud/test-runner:v0.0.4\n" +
+                "  RESOURCE_LIMITS:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi\n" +
+                "  RESOURCE_REQUIRE:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi";
+
+        //new PortForward().startPortForward(Configs.VELA_NAMESPACE, Configs.VELA_POD_LABELS, Configs.PORT_FROWARD);
+        LinkedHashMap<String, Object> inputMap = yamlToMap(input);
+        RepoTest project = new RepoTest();
+        project.runTest(inputMap);
+
+        LinkedHashMap<String, Object> inputMap1 = yamlToMap(input);
+        GenerateReport generateReport = new GenerateReport();
+        Assert.assertTrue(generateReport.generateReportMarkDown(inputMap1));
+    }
+
+    @Test
+    public void testRocketmqRunTest() throws IOException, InterruptedException {
+        String input = "action: test\n" +
+                "CODE_PATH: java/e2e\n" +
+                "REPO_URL: https://github.com/apache/rocketmq-e2e/tree/main/java/e2e/src/test/java\n" +
+                "namespace: rocketmq-12345562-23315\n" +
+                "askConfig: ${{ secrets.ASK_CONFIG_VIRGINA }}\n" +
+                "API_VERSION: v1\n" +
+                "KIND: Pod\n" +
+                "RESTART_POLICY: Never\n" +
+                "ENV:\n" +
+                "  CODE: https://ghproxy.com/https://github.com/apache/rocketmq-e2e\n" +
+                "  BRANCH: master\n" +
+                "  CODE_PATH: golang \n" +
+                "  CMD: |\n" +
+                "    cd ../common &&  mvn -Prelease -DskipTests clean package -U \n" +
+                "    cd ../rocketmq-admintools && source bin/env.sh \n" +
+                "    cd ../golang && go get -u github.com/apache/rocketmq-clients/golang && gotestsum --junitfile ./target/surefire-reports/TEST-report.xml ./mqgotest/... -timeout 2m  -v\n" +
+                "  ALL_IP: null\n" +
+                "CONTAINER:\n" +
+                "  IMAGE: cloudnativeofalibabacloud/test-runner:v0.0.3\n" +
+                "  RESOURCE_LIMITS:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi\n" +
+                "  RESOURCE_REQUIRE:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi";
+
+        //new PortForward().startPortForward(Configs.VELA_NAMESPACE, Configs.VELA_POD_LABELS, Configs.PORT_FROWARD);
+        LinkedHashMap<String, Object> inputMap = yamlToMap(input);
+
+        RepoTest project = new RepoTest();
+        Assert.assertTrue(project.runTest(inputMap));
+
+    }
+
+    @Test
+    public void testRocketmqCsharpRunTest() throws InterruptedException, IOException {
+        String input = "action: test\n" +
+                "namespace: rocketmq-12345562-3\n" +
+                "askConfig: ${{ secrets.ASK_CONFIG_VIRGINA }}\n" +
+                "API_VERSION: v1\n" +
+                "KIND: Pod\n" +
+                "RESTART_POLICY: Never\n" +
+                "ENV:\n" +
+                "  REPO_NAME: apache/rocketmq-e2e\n" +
+                "  CODE: https://ghproxy.com/https://github.com/apache/rocketmq-e2e\n" +
+                "  BRANCH: master\n" +
+                "  CODE_PATH: csharp \n" +
+                "  CMD: |\n" +
+                "    cd ../common &&  mvn -Prelease -DskipTests clean package -U\n" +
+                "    cd ../rocketmq-admintools && source bin/env.sh\n" +
+                "    cd ../csharp/rocketmq-client-csharp-tests/ && dotnet test --logger:\"junit;LogFilePath=../target/surefire-reports/TEST-result.xml\" -l \"console;verbosity=detailed\" \n" +
+                "  ALL_IP: null\n" +
+                "CONTAINER:\n" +
+                "  IMAGE: cloudnativeofalibabacloud/test-runner:v0.0.4\n" +
+                "  RESOURCE_LIMITS:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi\n" +
+                "  RESOURCE_REQUIRE:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi";
+
+        //new PortForward().startPortForward(Configs.VELA_NAMESPACE, Configs.VELA_POD_LABELS, Configs.PORT_FROWARD);
+
+        LinkedHashMap<String, Object> inputMap = yamlToMap(input);
+
+        RepoTest project = new RepoTest();
+        Assert.assertTrue(project.runTest(inputMap));
+
+        LinkedHashMap<String, Object> inputMap1 = yamlToMap(input);
+
+        GenerateReport generateReport = new GenerateReport();
+        Assert.assertTrue(generateReport.generateReportMarkDown(inputMap1));
+
+    }
+}
diff --git a/src/test/java/org/apache/process/action/reportUtilsTest.java b/src/test/java/org/apache/process/action/reportUtilsTest.java
new file mode 100644
index 0000000..4c32ea3
--- /dev/null
+++ b/src/test/java/org/apache/process/action/reportUtilsTest.java
@@ -0,0 +1,118 @@
+package org.apache.process.action;
+
+import org.junit.Assert;
+import org.junit.Test;
+import java.util.LinkedHashMap;
+
+import static org.apache.process.utils.GetParam.yamlToMap;
+
+public class reportUtilsTest {
+    @Test
+    public void testReportUtils() {
+        String input = "action: test\n" +
+                "CODE_PATH: java/e2e\n" +
+                "REPO_URL: https://github.com/apache/rocketmq-e2e/tree/main/java/e2e/src/test/java\n" +
+                "namespace: rocketmq-12345562-23315\n" +
+                "askConfig: ${{ secrets.ASK_CONFIG_VIRGINA }}\n" +
+                "API_VERSION: v1\n" +
+                "KIND: Pod\n" +
+                "RESTART_POLICY: Never\n" +
+                "ENV:\n" +
+                "  REPO_NAME: Wuyunfan-BUPT/rocketmq-e2e\n" +
+                "  CODE: https://ghproxy.com/https://github.com/Wuyunfan-BUPT/rocketmq-e2e\n" +
+                "  BRANCH: master\n" +
+                "  CODE_PATH: golang\n" +
+                "  CMD: |\n" +
+                "    cd ../common &&  mvn -Prelease -DskipTests clean package -U \n" +
+                "    cd ../rocketmq-admintools && source bin/env.sh \n" +
+                "    cd ../golang && go get -u github.com/apache/rocketmq-clients/golang && gotestsum --junitfile ./target/surefire-reports/TEST-report.xml ./mqgotest/... -timeout 2m  -v\n" +
+                "  ALL_IP: null\n" +
+                "CONTAINER:\n" +
+                "  IMAGE: cloudnativeofalibabacloud/test-runner:v0.0.3\n" +
+                "  RESOURCE_LIMITS:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi\n" +
+                "  RESOURCE_REQUIRE:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi";
+
+        LinkedHashMap<String, Object> inputMap = yamlToMap(input);
+
+        GenerateReport generateReport = new GenerateReport();
+       generateReport.generateReportMarkDown(inputMap);
+    }
+
+    @Test
+    public void testReportUtilsNacos() {
+        String input = "action: test\n" +
+                "CODE_PATH: java/nacos-2X\n" +
+                "namespace: rocketmq-12345562-23315\n" +
+                "askConfig: ${{ secrets.ASK_CONFIG_VIRGINA }}\n" +
+                "API_VERSION: v1\n" +
+                "KIND: Pod\n" +
+                "RESTART_POLICY: Never\n" +
+                "ENV:\n" +
+                "  REPO_NAME: Wuyunfan-BUPT/nacos-e2e\n" +
+                "  CODE: https://ghproxy.com/https://github.com/Wuyunfan-BUPT/nacos-e2e\n" +
+                "  BRANCH: main\n" +
+                "  CODE_PATH: java/nacos-2X\n" +
+                "  CMD: |\n" +
+                "    cd ../common &&  mvn -Prelease -DskipTests clean package -U \n" +
+                "    cd ../rocketmq-admintools && source bin/env.sh \n" +
+                "    cd ../golang && go get -u github.com/apache/rocketmq-clients/golang && gotestsum --junitfile ./target/surefire-reports/TEST-report.xml ./mqgotest/... -timeout 2m  -v\n" +
+                "  ALL_IP: null\n" +
+                "CONTAINER:\n" +
+                "  IMAGE: cloudnativeofalibabacloud/test-runner:v0.0.3\n" +
+                "  RESOURCE_LIMITS:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi\n" +
+                "  RESOURCE_REQUIRE:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi";
+
+        LinkedHashMap<String, Object> inputMap = yamlToMap(input);
+
+        GenerateReport generateReport = new GenerateReport();
+        Assert.assertTrue(generateReport.generateReportMarkDown(inputMap));
+    }
+
+    @Test
+    public void testRocketmqCsharpRunTest(){
+        String input = "action: test\n" +
+                "namespace: rocketmq-12345562-3\n" +
+                "askConfig: ${{ secrets.ASK_CONFIG_VIRGINA }}\n" +
+                "API_VERSION: v1\n" +
+                "KIND: Pod\n" +
+                "RESTART_POLICY: Never\n" +
+                "ENV:\n" +
+                "  REPO_NAME: apache/rocketmq-e2e\n" +
+                "  CODE: https://ghproxy.com/https://github.com/apache/rocketmq-e2e\n" +
+                "  BRANCH: master\n" +
+                "  CODE_PATH: csharp \n" +
+                "  CMD: |\n" +
+                "    cd ../common &&  mvn -Prelease -DskipTests clean package -U\n" +
+                "    cd ../rocketmq-admintools && source bin/env.sh\n" +
+                "    cd ../csharp/rocketmq-client-csharp-tests/ && dotnet test --logger:\"junit;LogFilePath=../target/surefire-reports/TEST-result.xml\" -l \"console;verbosity=detailed\" \n" +
+                "  ALL_IP: null\n" +
+                "CONTAINER:\n" +
+                "  IMAGE: cloudnativeofalibabacloud/test-runner:v0.0.4\n" +
+                "  RESOURCE_LIMITS:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi\n" +
+                "  RESOURCE_REQUIRE:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi";
+
+        LinkedHashMap<String, Object> inputMap = yamlToMap(input);
+
+        GenerateReport generateReport = new GenerateReport();
+        Assert.assertTrue(generateReport.generateReportMarkDown(inputMap));
+    }
+
+    @Test
+    public void testSpliHttp(){
+        GenerateReport generateReport = new GenerateReport();
+        String input = "https://ghproxy.com/https://github.com/apache/rocketmq-e2e";
+        System.out.println(generateReport.splitHttps(input));
+    }
+}
diff --git a/src/test/java/org/apache/process/utils/ConfigUtilsTest.java b/src/test/java/org/apache/process/utils/ConfigUtilsTest.java
new file mode 100644
index 0000000..c7a06db
--- /dev/null
+++ b/src/test/java/org/apache/process/utils/ConfigUtilsTest.java
@@ -0,0 +1,21 @@
+package org.apache.process.utils;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+public class ConfigUtilsTest {
+    @Test
+    public void testBase64Decoder() throws IOException {
+        String usrHome = System.getProperty("user.home");
+        String fileName = String.format("%s/.kube/config", usrHome);
+        String config = new String(Files.readAllBytes(Paths.get(fileName)));
+
+        String encoderFileName = String.format("%s/project/config_base64_new", usrHome);
+        String encoderString = new String(Files.readAllBytes(Paths.get(encoderFileName)));
+        Assert.assertEquals(config, new ConfigUtils().base64Decoder(encoderString));
+    }
+}
diff --git a/src/test/java/org/apache/process/utils/GetParamTest.java b/src/test/java/org/apache/process/utils/GetParamTest.java
new file mode 100644
index 0000000..6eb4f1c
--- /dev/null
+++ b/src/test/java/org/apache/process/utils/GetParamTest.java
@@ -0,0 +1,125 @@
+package org.apache.process.utils;
+
+import junit.framework.Assert;
+import org.apache.process.model.Deploymodel;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+
+import static org.apache.process.utils.GetParam.*;
+
+public class GetParamTest {
+    @Test
+    public void testyamlToMap() {
+        String input = "action: test\n" +
+                "namespace: nacos-12345562-2331\n" +
+                "ask-config: \"${{ secrets.ASK_CONFIG_VIRGINA }}\"\n" +
+                "API_VERSION: \"v1\"\n" +
+                "KIND: \"Pod\"\n" +
+                "RESTART_POLICY: \"Never\" \n" +
+                "ENV:\n" +
+                "  CODE: https://github.com/nacos-group/nacos-e2e.git\n" +
+                "  BRANCH: master\n" +
+                "  CODE_PATH: java/nacos-2X\n" +
+                "  CMD: mvn clean test -B -Dnacos.client.version=2.2.3\n" +
+                "  ALL_IP: null\n" +
+                "CONTAINER:\n" +
+                "  IMAGE: \"cloudnativeofalibabacloud/test-runner:v0.0.1\"\n" +
+                "  RESOURCE_LIMITS:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi\n" +
+                "  RESOURCE_REQUIRE:\n" +
+                "    cpu: 8\n" +
+                "    memory: 8Gi";
+
+        LinkedHashMap<String, Object> paramsMap = yamlToMap(input);
+        Assert.assertEquals(paramsMap.get("action"), "test");
+        Assert.assertEquals(paramsMap.get("namespace"), "nacos-12345562-2331");
+        Assert.assertEquals(paramsMap.get("ask-config"), "${{ secrets.ASK_CONFIG_VIRGINA }}");
+        Assert.assertEquals(paramsMap.get("API_VERSION"), "v1");
+        Assert.assertEquals(paramsMap.get("KIND"), "Pod");
+        Assert.assertEquals(paramsMap.get("RESTART_POLICY"), "Never");
+
+
+        LinkedHashMap<String, Object> envMap = (LinkedHashMap) paramsMap.get("ENV");
+        Assert.assertEquals(envMap.get("CODE"), "https://github.com/nacos-group/nacos-e2e.git");
+        Assert.assertEquals(envMap.get("BRANCH"), "master");
+        Assert.assertEquals(envMap.get("CODE_PATH"), "java/nacos-2X");
+        Assert.assertEquals(envMap.get("CMD"), "mvn clean test -B");
+        Assert.assertNull(envMap.get("ALL_IP"));
+
+
+        LinkedHashMap<String, Object> containerMap = (LinkedHashMap) paramsMap.get("CONTAINER");
+        Assert.assertEquals(containerMap.get("IMAGE"), "cloudnativeofalibabacloud/test-runner:v0.0.1");
+
+        LinkedHashMap<String, Object> limitsMap = (LinkedHashMap) containerMap.get("RESOURCE_LIMITS");
+        Assert.assertEquals(limitsMap.get("cpu").toString(), "8");
+        Assert.assertEquals(limitsMap.get("memory"), "8Gi");
+
+        LinkedHashMap<String, Object> requireMap = (LinkedHashMap) containerMap.get("RESOURCE_REQUIRE");
+        Assert.assertEquals(requireMap.get("cpu").toString(), "8");
+        Assert.assertEquals(requireMap.get("memory"), "8Gi");
+    }
+
+    @Test
+    public void testParseDeployInput() {
+        String helmvalues =
+                "global:\n" +
+                        "  mode: standalone\n" +
+                        "nacos:\n" +
+                        "  replicaCount: 1\n" +
+                        "  image: \n" +
+                        "    repository: wuyfeedocker/nacos-ci\n" +
+                        "    tag: develop-4f26def4-ccb0-45e5-9989-874e78424bea-8\n" +
+                        "  storage:\n" +
+                        "    type: embedded\n" +
+                        "    db:\n" +
+                        "      port: 3306\n" +
+                        "      username: nacos\n" +
+                        "      password: nacos\n" +
+                        "      param: characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false\n" +
+                        "service:\n" +
+                        "  nodePort: 30009\n" +
+                        "  type: ClusterIP";
+        String params = "action: deploy\n" +
+                "namespace: nacos-12345562-2331\n" +
+                "velaUsername: *****\n" +
+                "velaPassword: ******\n" +
+                "askConfig: ${{ secrets.ASK_CONFIG_VIRGINA }}\n" +
+                "helm:\n" +
+                "  chart: ./helm\n" +
+                "  git:\n" +
+                "    branch: master\n" +
+                "  repoType: git\n" +
+                "  retries: 3\n" +
+                "  url: https://ghproxy.com/https://github.com/apache/rocketmq-e2e.git\n" +
+                "  values:\n" +
+                "    global:\n" +
+                "      mode: standalone\n" +
+                "    nacos:\n" +
+                "      replicaCount: 1\n" +
+                "      image: \n" +
+                "        repository: wuyfeedocker/nacos-ci\n" +
+                "        tag: develop-4f26def4-ccb0-45e5-9989-874e78424bea-8\n" +
+                "      storage:\n" +
+                "        type: embedded\n" +
+                "        db:\n" +
+                "          port: 3306\n" +
+                "          username: nacos\n" +
+                "          password: nacos\n" +
+                "          param: characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false\n" +
+                "    service:\n" +
+                "      nodePort: 30009\n" +
+                "      type: ClusterIP";
+        String target = "helm";
+        HashMap<String, Object> paramsMap = parseDeployInput(params, target);
+        Assert.assertEquals(paramsMap.get("action"), "deploy");
+        Assert.assertEquals(paramsMap.get("namespace"), "nacos-12345562-2331");
+        Assert.assertEquals(paramsMap.get("velaUsername"), "****");
+        Assert.assertEquals(paramsMap.get("velaPassword"), "*****");
+        Assert.assertEquals(paramsMap.get("askConfig"), "${{ secrets.ASK_CONFIG_VIRGINA }}");
+
+        Assert.assertEquals(Deploymodel.generateComponentProperties(helmvalues, "./helm", "master", "https://ghproxy.com/https://github.com/apache/rocketmq-e2e.git").length(), paramsMap.get("helm").toString().length());
+    }
+}
diff --git a/test-runner/Dockerfile-test-runner b/test-runner/Dockerfile-test-runner
deleted file mode 100644
index c7eed78..0000000
--- a/test-runner/Dockerfile-test-runner
+++ /dev/null
@@ -1,106 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-#
-FROM eclipse-temurin:8-jdk-centos7
-
-ENV LANG C.UTF-8
-
-WORKDIR /root
-COPY code_run.sh /root/code_run.sh
-RUN wget https://dlcdn.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz
-RUN chmod 755 /root/code_run.sh
-RUN tar -xvf /root/apache-maven-3.8.8-bin.tar.gz -C /opt/
-
-RUN yum install unzip curl git epel-release gettext jq wget psmisc -y
-RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
-RUN install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
-RUN curl -fsSl https://kubevela.net/script/install.sh | bash
-
-RUN wget -O go1.19.linux-amd64.tar.gz -q https://go.dev/dl/go1.19.linux-amd64.tar.gz && \
-    tar -C /usr/local -xzf go1.19.linux-amd64.tar.gz && \
-    rm go1.19.linux-amd64.tar.gz
-
-RUN echo "export GOROOT=/usr/local/go" >> /root/.bashrc && \
-    echo "export PATH=\$GOROOT/bin:\$PATH" >> /root/.bashrc && \
-    echo "export GOPATH=/home/admin/code" >> /root/.bashrc && \
-    echo "export GOPROXY=https://proxy.golang.com.cn,direct" >> /root/.bashrc && \
-    echo "export GO111MODULE=on" >> /root/.bashrc && \
-    echo "export GOSUMDB=off" >> /root/.bashrc && \
-    echo "export GONOSUMDB=*.corp.example.com,rsc.io/private" >> /root/.bashrc && \
-    source /root/.bashrc
-
-RUN yum install -y centos-release-scl && \
-    yum install -y devtoolset-7-gcc devtoolset-7-gcc-c++
-
-RUN echo "source /opt/rh/devtoolset-7/enable" >> /root/.bashrc && \
-    source /root/.bashrc
-
-ENV PATH="${PATH}:/opt/rh/devtoolset-7/root/usr/bin:/usr/local/go/bin"
-
-RUN GO111MODULE="on" go install gotest.tools/gotestsum@latest && \
-    ln -s /root/go/bin/gotestsum /usr/bin/gotestsum
-
-RUN yum  -y install make  && \
-    yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel && \
-    wget http://pasmicrosservice.oss-cn-hangzhou.aliyuncs.com/tools/Python-3.9.0.tgz && \
-    mkdir /usr/local/python3  && \
-    tar -zxvf Python-3.9.0.tgz && \
-    cd Python-3.9.0 && \
-    ./configure --prefix=/usr/local/python3  && \
-    make && make install && \
-    ln -s /usr/local/python3/bin/python3 /usr/bin/python3 && \
-    ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3 && \
-    pip3 install pytest && \
-    ln -s /usr/local/python3/bin/pytest /usr/bin/pytest && \
-    cd -  && \
-    rm -rf Python-3.9.0.tgz
-
-RUN pip3 install cmake && \
-    ln -s /usr/local/python3/bin/cmake /usr/bin/cmake
-
-RUN git clone https://github.com/google/googletest  && \
-    cd googletest && \
-    cmake CMakeLists.txt && \
-    make && \
-    cp lib/libgtest*.a  /usr/lib && \
-    cp -a googletest/include/gtest/ /usr/include/ && \
-    cd - && \
-    rm -rf googletest
-
-RUN cd /root && \
-    wget http://pasmicrosservice.oss-cn-hangzhou.aliyuncs.com/tools/node-v10.16.0-linux-x64.tar.xz && \
-    xz -d node-v10.16.0-linux-x64.tar.xz && \
-    tar -xf node-v10.16.0-linux-x64.tar && \
-    chown -R root:root /root/node-v10.16.0-linux-x64 && \
-    cd node-v10.16.0-linux-x64/bin  && \
-    ln -s /root/node-v10.16.0-linux-x64/bin/node /usr/local/bin/node && \
-    ln -s /root/node-v10.16.0-linux-x64/bin/npm /usr/local/bin/npm && \
-    rm -rf node-v10.16.0-linux-x64.tar && \
-    cd -
-
-ENV PATH="${PATH}:/usr/local/bin"
-
-RUN npm install -g mocha-junit-reporter && \
-    npm install -g mocha@3.5.3 && \
-    ln -s /root/node-v10.16.0-linux-x64/bin/mocha /bin/mocha
-
-RUN rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm && \
-    yum install -y dotnet-sdk-3.1
-
-RUN echo "export PYTHONPATH=$PYTHONPATH:/root/code/" >> /root/.bashrc
-
-WORKDIR /root
-CMD ["/bin/bash", "-c", "./code_run.sh"]
diff --git a/test-runner/code_run.sh b/test-runner/code_run.sh
deleted file mode 100644
index 2141efc..0000000
--- a/test-runner/code_run.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/sh
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-#
-
-export M2_HOME=/opt/apache-maven-3.8.8
-export MAVEN_HOME=/opt/apache-maven-3.8.8
-export PATH=${M2_HOME}/bin:${PATH}
-
-git clone $CODE -b $BRANCH code
-
-cd code
-cd $CODE_PATH
-script_name=$(uuidgen).sh
-echo -e "$CMD" > $script_name
-sh $script_name > /root/testlog.txt 2>&1
-res=$?
-# wait for result collect
-touch /root/testdone
-sleep 60
-exit $res
diff --git a/test-runner/settings.xml b/test-runner/settings.xml
deleted file mode 100644
index 3ba0b82..0000000
--- a/test-runner/settings.xml
+++ /dev/null
@@ -1,270 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you 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.
--->
-
-<!--
- | This is the configuration file for Maven. It can be specified at two levels:
- |
- |  1. User Level. This settings.xml file provides configuration for a single user,
- |                 and is normally provided in ${user.home}/.m2/settings.xml.
- |
- |                 NOTE: This location can be overridden with the CLI option:
- |
- |                 -s /path/to/user/settings.xml
- |
- |  2. Global Level. This settings.xml file provides configuration for all Maven
- |                 users on a machine (assuming they're all using the same Maven
- |                 installation). It's normally provided in
- |                 ${maven.conf}/settings.xml.
- |
- |                 NOTE: This location can be overridden with the CLI option:
- |
- |                 -gs /path/to/global/settings.xml
- |
- | The sections in this sample file are intended to give you a running start at
- | getting the most out of your Maven installation. Where appropriate, the default
- | values (values used when the setting is not specified) are provided.
- |
- |-->
-<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
-          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
-    <!-- localRepository
-     | The path to the local repository maven will use to store artifacts.
-     |
-     | Default: ${user.home}/.m2/repository
-    <localRepository>/path/to/local/repo</localRepository>
-    -->
-
-    <!-- interactiveMode
-     | This will determine whether maven prompts you when it needs input. If set to false,
-     | maven will use a sensible default value, perhaps based on some other setting, for
-     | the parameter in question.
-     |
-     | Default: true
-    <interactiveMode>true</interactiveMode>
-    -->
-
-    <!-- offline
-     | Determines whether maven should attempt to connect to the network when executing a build.
-     | This will have an effect on artifact downloads, artifact deployment, and others.
-     |
-     | Default: false
-    <offline>false</offline>
-    -->
-
-    <!-- pluginGroups
-     | This is a list of additional group identifiers that will be searched when resolving plugins by their prefix, i.e.
-     | when invoking a command line like "mvn prefix:goal". Maven will automatically add the group identifiers
-     | "org.apache.maven.plugins" and "org.codehaus.mojo" if these are not already contained in the list.
-     |-->
-    <pluginGroups>
-        <!-- pluginGroup
-         | Specifies a further group identifier to use for plugin lookup.
-        <pluginGroup>com.your.plugins</pluginGroup>
-        -->
-    </pluginGroups>
-
-    <!-- proxies
-     | This is a list of proxies which can be used on this machine to connect to the network.
-     | Unless otherwise specified (by system property or command-line switch), the first proxy
-     | specification in this list marked as active will be used.
-     |-->
-    <proxies>
-        <!-- proxy
-         | Specification for one proxy, to be used in connecting to the network.
-         |
-        <proxy>
-          <id>optional</id>
-          <active>true</active>
-          <protocol>http</protocol>
-          <username>proxyuser</username>
-          <password>proxypass</password>
-          <host>proxy.host.net</host>
-          <port>80</port>
-          <nonProxyHosts>local.net|some.host.com</nonProxyHosts>
-        </proxy>
-        -->
-    </proxies>
-
-    <!-- servers
-     | This is a list of authentication profiles, keyed by the server-id used within the system.
-     | Authentication profiles can be used whenever maven must make a connection to a remote server.
-     |-->
-    <servers>
-        <!-- server
-         | Specifies the authentication information to use when connecting to a particular server, identified by
-         | a unique name within the system (referred to by the 'id' attribute below).
-         |
-         | NOTE: You should either specify username/password OR privateKey/passphrase, since these pairings are
-         |       used together.
-         |
-        <server>
-          <id>deploymentRepo</id>
-          <username>repouser</username>
-          <password>repopwd</password>
-        </server>
-        -->
-
-        <!-- Another sample, using keys to authenticate.
-        <server>
-          <id>siteServer</id>
-          <privateKey>/path/to/private/key</privateKey>
-          <passphrase>optional; leave empty if not used.</passphrase>
-        </server>
-        -->
-    </servers>
-
-    <!-- mirrors
-     | This is a list of mirrors to be used in downloading artifacts from remote repositories.
-     |
-     | It works like this: a POM may declare a repository to use in resolving certain artifacts.
-     | However, this repository may have problems with heavy traffic at times, so people have mirrored
-     | it to several places.
-     |
-     | That repository definition will have a unique id, so we can create a mirror reference for that
-     | repository, to be used as an alternate download site. The mirror site will be the preferred
-     | server for that repository.
-     |-->
-    <mirrors>
-        <!-- mirror
-         | Specifies a repository mirror site to use instead of a given repository. The repository that
-         | this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
-         | for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
-         |
-        <mirror>
-          <id>mirrorId</id>
-          <mirrorOf>repositoryId</mirrorOf>
-          <name>Human Readable Name for this Mirror.</name>
-          <url>http://my.repository.com/repo/path</url>
-        </mirror>
-         -->
-        <mirror>
-            <id>maven-default-http-blocker</id>
-            <mirrorOf>external:http:*</mirrorOf>
-            <name>Pseudo repository to mirror external repositories initially using HTTP.</name>
-            <url>http://0.0.0.0/</url>
-            <blocked>true</blocked>
-        </mirror>
-        <mirror>
-            <id>aliyunmaven</id>
-            <mirrorOf>*</mirrorOf>
-            <name>aliyun</name>
-            <url>https://maven.aliyun.com/repository/public</url>
-        </mirror>
-    </mirrors>
-
-    <!-- profiles
-     | This is a list of profiles which can be activated in a variety of ways, and which can modify
-     | the build process. Profiles provided in the settings.xml are intended to provide local machine-
-     | specific paths and repository locations which allow the build to work in the local environment.
-     |
-     | For example, if you have an integration testing plugin - like cactus - that needs to know where
-     | your Tomcat instance is installed, you can provide a variable here such that the variable is
-     | dereferenced during the build process to configure the cactus plugin.
-     |
-     | As noted above, profiles can be activated in a variety of ways. One way - the activeProfiles
-     | section of this document (settings.xml) - will be discussed later. Another way essentially
-     | relies on the detection of a system property, either matching a particular value for the property,
-     | or merely testing its existence. Profiles can also be activated by JDK version prefix, where a
-     | value of '1.4' might activate a profile when the build is executed on a JDK version of '1.4.2_07'.
-     | Finally, the list of active profiles can be specified directly from the command line.
-     |
-     | NOTE: For profiles defined in the settings.xml, you are restricted to specifying only artifact
-     |       repositories, plugin repositories, and free-form properties to be used as configuration
-     |       variables for plugins in the POM.
-     |
-     |-->
-    <profiles>
-        <!-- profile
-         | Specifies a set of introductions to the build process, to be activated using one or more of the
-         | mechanisms described above. For inheritance purposes, and to activate profiles via <activatedProfiles/>
-         | or the command line, profiles have to have an ID that is unique.
-         |
-         | An encouraged best practice for profile identification is to use a consistent naming convention
-         | for profiles, such as 'env-dev', 'env-test', 'env-production', 'user-jdcasey', 'user-brett', etc.
-         | This will make it more intuitive to understand what the set of introduced profiles is attempting
-         | to accomplish, particularly when you only have a list of profile id's for debug.
-         |
-         | This profile example uses the JDK version to trigger activation, and provides a JDK-specific repo.
-        <profile>
-          <id>jdk-1.4</id>
-
-          <activation>
-            <jdk>1.4</jdk>
-          </activation>
-
-          <repositories>
-            <repository>
-              <id>jdk14</id>
-              <name>Repository for JDK 1.4 builds</name>
-              <url>http://www.myhost.com/maven/jdk14</url>
-              <layout>default</layout>
-              <snapshotPolicy>always</snapshotPolicy>
-            </repository>
-          </repositories>
-        </profile>
-        -->
-
-        <!--
-         | Here is another profile, activated by the system property 'target-env' with a value of 'dev',
-         | which provides a specific path to the Tomcat instance. To use this, your plugin configuration
-         | might hypothetically look like:
-         |
-         | ...
-         | <plugin>
-         |   <groupId>org.myco.myplugins</groupId>
-         |   <artifactId>myplugin</artifactId>
-         |
-         |   <configuration>
-         |     <tomcatLocation>${tomcatPath}</tomcatLocation>
-         |   </configuration>
-         | </plugin>
-         | ...
-         |
-         | NOTE: If you just wanted to inject this configuration whenever someone set 'target-env' to
-         |       anything, you could just leave off the <value/> inside the activation-property.
-         |
-        <profile>
-          <id>env-dev</id>
-
-          <activation>
-            <property>
-              <name>target-env</name>
-              <value>dev</value>
-            </property>
-          </activation>
-
-          <properties>
-            <tomcatPath>/path/to/tomcat/instance</tomcatPath>
-          </properties>
-        </profile>
-        -->
-    </profiles>
-
-    <!-- activeProfiles
-     | List of profiles that are active for all builds.
-     |
-    <activeProfiles>
-      <activeProfile>alwaysActiveProfile</activeProfile>
-      <activeProfile>anotherAlwaysActiveProfile</activeProfile>
-    </activeProfiles>
-    -->
-</settings>
\ No newline at end of file