Parcourir la source

添加缺失的文件

添加缺失的文件
zhiqiang.yu il y a 1 mois
Parent
commit
5756d0658f

+ 29 - 0
shareLibs/gitUtil.groovy

@@ -0,0 +1,29 @@
+def clone2(String projAddr, String projCredentialId, String base_branch) {
+    echo "-----> Check out project source from branch($base_branch)..."
+    dir("projdir"){
+        // git branch: "$base_branch", 
+        //     credentialsId: "${projCredentialId}",
+        //     url: "${projAddr}"
+        checkout([$class: 'GitSCM',
+                branches: [[name: "$base_branch"]],
+                doGenerateSubmoduleConfigurations: false,
+                extensions: [[$class: 'CleanBeforeCheckout'], [$class: 'CloneOption', depth: 1, noTags: false, reference: '', shallow: true, timeout: 240]],
+                submoduleCfg: [],
+                userRemoteConfigs: [[credentialsId: "${projCredentialId}", url: "${projAddr}"]]
+            ])
+        COMMIT_SHA = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
+    }
+}
+
+def clone(Object gitConf, String base_branch) {
+    
+    echo "-----> Check out project source from branch($base_branch)..."
+    dir("projdir"){
+        git branch: "$base_branch", 
+            credentialsId: "${gitConf.credentialId}",
+            url: "${gitConf.address}"
+        COMMIT_SHA = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
+    }
+}
+
+return this

+ 47 - 0
shareLibs/imageBuilder.groovy

@@ -0,0 +1,47 @@
+def buildJar() {
+    
+    echo "-----> Building java package ..."
+    dir("projdir"){
+        sh '$MAVEN_HOME/bin/mvn clean package -Dmaven.test.skip=true'
+    }
+}
+
+def buildImage(Object SERVICE, Object DOCKER, String base_branch, String module='') {
+    echo "-----> Building docker image: ${DOCKER.registry}/${DOCKER.image}:$base_branch-$BUILD_NUMBER ..."
+    dir("projdir") {
+        sh """cat > Dockerfile<<EOF
+FROM openjdk:8-alpine as final
+ADD ${module}target/${SERVICE.jar} /app/target/${SERVICE.jar}
+ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-Xmx1024m","-Dserver.port=80","-jar","/app/target/${SERVICE.jar}", "--spring.config.location=file:/app/configs/application.yml"]
+EOF
+"""
+        docker.withRegistry("http://${DOCKER.registry}", "${DOCKER.push_credentialId}") {
+        def image = docker.build("${DOCKER.image}:$base_branch-$BUILD_NUMBER")
+            image.push()
+            sh "docker rmi ${DOCKER.image}:${base_branch}-${BUILD_NUMBER}"
+            sh "docker rmi ${DOCKER.registry}/${DOCKER.image}:${base_branch}-${BUILD_NUMBER}"
+        }
+    }
+}
+
+def buildImage2(String jar, String registry, String docker_image, String registry_push_credentialId, String base_branch) {
+
+    echo "-----> Building docker image: $registry/$docker_image:$base_branch-$BUILD_NUMBER ..."
+    dir("projdir"){
+        sh """cat > Dockerfile<<EOF
+FROM openjdk:8-alpine as final
+ADD target/${jar} /app/target/${jar}
+ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-Xmx1024m","-Dserver.port=80","-jar","/app/target/${jar}", "--spring.config.location=file:/app/configs/application.yml"]
+EOF
+"""
+        docker.withRegistry("http://${registry}", "${registry_push_credentialId}") {
+            def image = docker.build("$docker_image:$base_branch-$BUILD_NUMBER")
+            image.push()
+            sh "docker rmi ${docker_image}:${base_branch}-${BUILD_NUMBER}"
+            sh "docker rmi ${registry}/${docker_image}:${base_branch}-${BUILD_NUMBER}"
+        }
+    }
+}
+
+
+return this

+ 129 - 0
shareLibs/k3sDeploy.groovy

@@ -0,0 +1,129 @@
+def applyConfigMapEnv(Object SERVICE, Object K3S, String configMapPath='projdir/configmap-env') {
+
+  configmap_env_name = "${SERVICE.name}${SERVICE.version}-env"
+  configmap_env = 'coresvc-configmap-env.yaml'
+  sh "/k3s/kubectl create configmap ${configmap_env_name} --dry-run=client --from-env-file=${configMapPath} -o yaml > ${configmap_env}"
+  sh "/k3s/kubectl --kubeconfig ${env.WORKSPACE}/${K3S.kubeconfig} apply -f ${configmap_env}"
+}
+
+def applyConfigMap(Object SERVICE, Object K3S, String configMapPath = './configmap/') {
+
+  configmap_name = "${SERVICE.name}${SERVICE.version}-config"
+  configmap_file = "${SERVICE.name}-configmap.yaml"
+  sh "/k3s/kubectl create configmap ${configmap_name} --dry-run=client --from-file=${configMapPath} -o yaml > ${configmap_file}"
+  sh "/k3s/kubectl --kubeconfig ${env.WORKSPACE}/${K3S.kubeconfig} apply -f ${configmap_file}"
+}
+
+def applyDeployment(Object SERVICE, Object K3S, Object DOCKER,
+    String release, String COMMIT_SHA, String base_branch, String args) {
+
+    // NO configmap_file
+    sh """cat > deployment.yml<<EOF
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: ${SERVICE.name}${SERVICE.version}
+  namespace: default
+spec:
+  selector:
+    matchLabels:
+      app: ${SERVICE.name}${SERVICE.version}
+      release: ${release}
+  template:
+    metadata:
+      labels:
+        app: ${SERVICE.name}${SERVICE.version}
+        release: ${release}
+      annotations:
+        commit-sha: "${COMMIT_SHA}"
+    spec:
+      imagePullSecrets:
+      - name: ${K3S.pull_secretId}
+      containers:
+      - name: ${SERVICE.name}${SERVICE.version}
+        image: ${DOCKER.registry}/${DOCKER.image}:${base_branch}-${env.BUILD_NUMBER}
+        command: ["java"]
+        args: ${args}
+        resources:
+          limits:
+            cpu: ${K3S.cpu_limits}
+          requests:
+            cpu: ${K3S.cpu_requests}
+        readinessProbe:
+          httpGet:
+            path: ${SERVICE.health}
+            port: 80
+          initialDelaySeconds: 15
+          periodSeconds: 30
+          failureThreshold: 3
+        ports:
+        - name: http
+          containerPort: 80
+        envFrom:
+        - configMapRef:
+            name: ${configmap_env_name}
+EOF
+"""
+    sh "/k3s/kubectl --kubeconfig ${env.WORKSPACE}/${K3S.kubeconfig} apply -f deployment.yml"
+}
+
+def applyDeploymentWithConfig(Object SERVICE, Object K3S, Object DOCKER,
+    String release, String COMMIT_SHA, String base_branch, String args) {
+    // else NEED to import configmap_file
+    sh """cat > deployment.yml<<EOF
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: ${SERVICE.name}${SERVICE.version}
+  namespace: default
+spec:
+  selector:
+    matchLabels:
+      app: ${SERVICE.name}${SERVICE.version}
+      release: ${release}
+  template:
+    metadata:
+      labels:
+        app: ${SERVICE.name}${SERVICE.version}
+        release: ${release}
+      annotations:
+        commit-sha: "${COMMIT_SHA}"
+    spec:
+      imagePullSecrets:
+      - name: ${K3S.pull_secretId}
+      containers:
+      - name: ${SERVICE.name}${SERVICE.version}
+        image: ${DOCKER.registry}/${DOCKER.image}:${base_branch}-${env.BUILD_NUMBER}
+        command: ["java"]
+        args: ${args}
+        resources:
+          limits:
+            cpu: ${K3S.cpu_limits}
+          requests:
+            cpu: ${K3S.cpu_requests}
+        readinessProbe:
+          httpGet:
+            path: /health
+            port: 80
+          initialDelaySeconds: 15
+          periodSeconds: 30
+          failureThreshold: 3
+        ports:
+        - name: http
+          containerPort: 80
+        envFrom:
+        - configMapRef:
+            name: ${configmap_env_name}
+        volumeMounts:
+        - name: config-volume
+          mountPath: /app/configs
+      volumes:
+      - name: config-volume
+        configMap:
+          name: ${configmap_name}
+EOF
+"""
+    sh "/k3s/kubectl --kubeconfig ${env.WORKSPACE}/${K3S.kubeconfig} apply -f deployment.yml"
+}
+
+return this

+ 21 - 0
shareLibs/yaml2Map.groovy

@@ -0,0 +1,21 @@
+//
+def read(String filePath) {
+  def pipelineCfg = readYaml(file: filePath)
+  return pipelineCfg
+}
+
+// 用project 覆盖 global
+def merge(Object project, Object global) {
+  global.git = project.git
+  global.service = project.service
+  
+  if (project.k3s != null)
+    global.k3s = project.k3s
+
+  if (project.docker != null)
+    global.docker = project.docker
+  
+  return global
+}
+
+return this

+ 13 - 0
sharedLibs/gitUtil.groovy

@@ -0,0 +1,13 @@
+def clone(Map gitConf, String base_branch, String path='projdir') {
+    
+    echo "-----> Check out project source from ${gitConf.address}:${base_branch}..."
+    dir(path){
+        git branch: "${base_branch}", 
+            credentialsId: "${gitConf.credentialId}",
+            url: "${gitConf.address}"
+        def COMMIT_SHA = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
+        return COMMIT_SHA
+    }
+}
+
+return this

+ 71 - 0
sharedLibs/imageBuilder.groovy

@@ -0,0 +1,71 @@
+// 获取镜像URL
+generateFullImageUri = {Map DOCKER, String base_branch, String BUILD_NUMBER ->
+    return "${DOCKER.registry}/${DOCKER.image}:${base_branch}-${BUILD_NUMBER}"
+}
+
+// 获取镜像名
+generateImageName = {Map DOCKER, String base_branch, String BUILD_NUMBER ->
+    return "${DOCKER.image}:${base_branch}-${BUILD_NUMBER}"
+}
+
+def generateDockerfile(String JAR, String module, String pathOfDockerfile='projdir') {
+    
+    module = module == null ? '' : (module[-1] == '/' ? module : module + '/')
+    sh """cat > ${pathOfDockerfile}/Dockerfile<<EOF
+FROM openjdk:8-alpine as final
+ADD ${module}target/${JAR} /app/target/${JAR}
+ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-Xmx1024m","-Dserver.port=80","-jar","/app/target/${JAR}", "--spring.config.location=file:/app/configs/application.yml"]
+EOF
+"""
+    return pathOfDockerfile
+}
+
+// 项目自带Dockerfile的时候,只需返回项目路径
+generateNodeJsDockerFile = { ... args ->
+    return 'projdir'
+}
+
+// Maven工程生成.jar
+def buildJar(String path='projdir') {
+    
+    echo "-----> Building java package ..."
+    dir(path){
+        sh '$MAVEN_HOME/bin/mvn clean package -Dmaven.test.skip=true'
+    }
+    return path
+}
+
+def cleanJar(String path='projdir') {
+    echo "-----> Clean java package ..."
+    dir(path){
+        sh '$MAVEN_HOME/bin/mvn clean'
+    }
+    return path
+}
+
+// 构建Docker镜像
+def buildDockerImage(Object SERVICE, Object DOCKER, String base_branch, Closure dockerfile=null, String filter2Remove=null) {
+
+    def imageUri = generateFullImageUri(DOCKER, base_branch, BUILD_NUMBER)
+    echo "-----> Building docker image: ${imageUri} ..."
+    def projDir = 'projdir'
+    if (dockerfile != null)
+        projDir = dockerfile(SERVICE.jar, SERVICE.module)
+    else
+        projDir = generateDockerfile(SERVICE.jar, SERVICE.module)
+
+    dir(projDir) {
+        docker.withRegistry("http://${DOCKER.registry}", "${DOCKER.push_credentialId}") {
+            def imageName = generateImageName(DOCKER, base_branch, BUILD_NUMBER)
+            def image = docker.build(imageName)
+            image.push()
+            sh "docker rmi ${imageName}"
+            sh "docker rmi ${imageUri}"
+            if(filter2Remove != null)
+                sh "docker image prune --filter label=${filter2Remove} -f"
+        }
+    }
+    return imageUri
+}
+
+return this

+ 97 - 0
sharedLibs/k3sUtil.groovy

@@ -0,0 +1,97 @@
+// 配置configMap Environment
+//   - SERVICE: service配置
+//   - K3S: k3s配置
+//   - configMapPath: 指定configMapPath,默认使用"projdir/configmap-env"
+def applyConfigMapEnv(Object SERVICE, Object K3S, String configMapPath='projdir/configmap-env.ini', String defaultNS='default') {
+
+  println "applying k3s configmap environment from ${configMapPath}"
+  configmap_env_name = "${SERVICE.name}${SERVICE.version}-env"
+  configmap_env = "${SERVICE.name}${SERVICE.version}-env.yaml"
+  sh "/k3s/kubectl create configmap ${configmap_env_name} --dry-run=client --from-env-file=${configMapPath} -o yaml > ${configmap_env}"
+  sh "/k3s/kubectl --kubeconfig ${env.WORKSPACE}/${K3S.kubeconfig} apply -f ${configmap_env} -n ${defaultNS}"
+  return configmap_env_name
+}
+
+// 配置configMap
+//   - SERVICE: service配置
+//   - K3S: k3s配置
+//   - configMapPath: 指定configMapPath,默认使用./configmap/
+def applyConfigMapConfig(Object SERVICE, Object K3S, String configMapPath = './configmap/', String defaultNS='default') {
+
+  println "applying k3s configmap config from ${configMapPath}"
+  configmap_name = "${SERVICE.name}${SERVICE.version}-config"
+  configmap_file = "${SERVICE.name}-configmap.yaml"
+  sh "/k3s/kubectl create configmap ${configmap_name} --dry-run=client --from-file=${configMapPath} -o yaml > ${configmap_file}"
+  sh "/k3s/kubectl --kubeconfig ${env.WORKSPACE}/${K3S.kubeconfig} apply -f ${configmap_file} -n ${defaultNS}"
+  return configmap_name
+}
+
+// 生成deployment.yaml
+//   - template: deployment(template).yaml模板文件路径
+//   - kvs: key/value替换deployment(template)
+def generateDeployment(Map kvs, String template="deployment.yaml") {
+  
+  if (!fileExists(template)) {
+    println "skip generating deployment.yaml, file ${template} not exists" 
+    return
+  }
+
+  println "generating k8s deployment... replace Key/Value in file: ${template}."
+  kvs.each {
+    def key, val -> 
+      print(val)
+      value = val
+        .replace('/', '\\/')
+        .replace('"', '\\"')
+      
+      sh "sed -i \"s/${key}/${value}/g\" ${template}"
+  }
+}
+
+// 生成service.yaml
+//   - template: service(template).yaml模板文件路径
+//   - kvs: key/value替换service(template).yaml
+def generateService(Map kvs, String template="service.yaml") {
+
+  if (!fileExists(template)) {
+    println "skip generating service.yaml, file ${template} not exists" 
+    return
+  }
+
+  println "generating k8s service.yaml... replace Key/Value in file: ${template}."
+  kvs.each {
+    def key, val -> 
+      print(val)
+      value = val
+        .replace('/', '\\/')
+        .replace('"', '\\"')
+      
+      sh "sed -i \"s/${key}/${value}/g\" ${template}"
+  }
+}
+
+// 配置deployment.yaml
+def applyDeployment(Object K3S, String deployment="deployment.yaml") {
+
+  if (!fileExists(deployment)) {
+    println "ignore, file ${deployment} not exists" 
+    return
+  }
+
+  println "applying k8s deployment file: ${deployment} ..."
+  sh "/k3s/kubectl --kubeconfig ${env.WORKSPACE}/${K3S.kubeconfig} apply -f ${deployment}"
+}
+
+// 配置service.yaml
+def applyService(K3S, String service="service.yaml") {
+
+  if (!fileExists(service)) {
+    println "ignore, file ${service} not exists" 
+    return
+  }
+
+  println "applying k8s deployment file: ${service} ..."
+  sh "/k3s/kubectl --kubeconfig ${env.WORKSPACE}/${K3S.kubeconfig} apply -f ${service}"
+}
+
+return this

+ 29 - 0
sharedLibs/notification.groovy

@@ -0,0 +1,29 @@
+def processString(String content) {
+                                return content
+                            }
+@NonCPS
+def collectChangeInfo() {
+    // 配置文件扩展名正则匹配
+    def configFilePatterns = ~/.*[\\/]*[^\\/]*\.(yaml|yml|ini|groovy)$/
+    
+    // 存储最后一个符合条件的变更
+    def lastCodeChange = ""
+    
+    def changeLogSets = currentBuild.changeSets
+    changeLogSets.each { changeSet ->
+        changeSet.items.each { entry ->
+            // 检查是否有非配置文件的变更
+            def isCodeChange = entry.affectedFiles.any { file ->
+                !(file.path ==~ configFilePatterns)
+            }
+            
+            if (isCodeChange) {
+                lastCodeChange += "Commit ${entry.commitId.substring(0, 8)} by ${entry.author} Info"
+                lastCodeChange += "${entry.msg}\n"
+            }
+        }
+    }
+    return lastCodeChange ? lastCodeChange.trim() : "No valid code changes found."
+}
+
+return this

+ 127 - 0
sharedLibs/stages.docker.groovy

@@ -0,0 +1,127 @@
+sharedLibsPath = "${env.WORKSPACE}/sharedLibs"
+
+// 获取镜像URL
+generateFullImageUri = {Map DOCKER, String base_branch, String BUILD_NUMBER ->
+    return "${DOCKER.registry}/${DOCKER.image}:${base_branch}-${BUILD_NUMBER}"
+}
+
+// 获取镜像名
+generateImageName = {Map DOCKER, String base_branch, String BUILD_NUMBER ->
+    return "${DOCKER.image}:${base_branch}-${BUILD_NUMBER}"
+}
+
+// 生成Dockerfile
+def generateDockerfile(Object SERVICE, String pathOfDockerfile='projdir') {
+    
+    def module = SERVICE.module == null ? '' : (SERVICE.module[-1] == '/' ? SERVICE.module : SERVICE.module + '/')
+    echo "-----> Generating Dockerfile: ${pathOfDockerfile}/Dockerfile ..."
+    sh """cat > ${pathOfDockerfile}/Dockerfile<<EOF
+FROM openjdk:8-alpine as final
+ADD ${module}target/${SERVICE.jar} /app/target/${SERVICE.jar}
+EOF
+"""
+    return pathOfDockerfile
+}
+
+// Maven工程生成.jar
+def buildJar(String path='projdir') {
+    
+    echo "-----> Building java package ..."
+    dir(path){
+        sh '$MAVEN_HOME/bin/mvn clean package -Dmaven.test.skip=true'
+    }
+    return path
+}
+
+def cleanJar(String path='projdir') {
+    echo "-----> Clean java package ..."
+    dir(path){
+        sh '$MAVEN_HOME/bin/mvn clean'
+    }
+    return path
+}
+
+// 构建Docker镜像
+def buildDockerImage(Object SERVICE, Object DOCKER, String base_branch, projDir='projdir', String filter2Remove=null) {
+
+    def imageUri = generateFullImageUri(DOCKER, base_branch, BUILD_NUMBER)
+    echo "-----> Building docker image: ${imageUri} ..."
+    dir(projDir) {
+        docker.withRegistry("http://${DOCKER.registry}", "${DOCKER.push_credentialId}") {
+            def imageName = generateImageName(DOCKER, base_branch, BUILD_NUMBER)
+            def image = docker.build(imageName)
+            image.push()
+            sh "docker rmi ${imageName}"
+            sh "docker rmi ${imageUri}"
+            if(filter2Remove != null)
+                sh "docker image prune --filter label=${filter2Remove} -f"
+        }
+    }
+    return imageUri
+}
+
+def dockerRun(Object CONFIG, String docker_image_uri) {
+
+    def DOCKER_OPTS = "-H tcp://10.172.9.200:2375"
+    sh "if [ \$(docker ${DOCKER_OPTS} ps -aq --filter name=${CONFIG.service.name}) ]; then docker ${DOCKER_OPTS} rm -f -v ${CONFIG.service.name};fi"
+    sh "docker ${DOCKER_OPTS} login -u ryo.hune@gmail.com -p QAZwsx117 registry.cn-shanghai.aliyuncs.com"
+    def EXEC = "docker ${DOCKER_OPTS} run -d --restart=always -p 80:80 -e TZ=Asia/Shanghai --name ${CONFIG.service.name} ${docker_image_uri} " + 
+        "java -Djava.awt.headless=true -Dserver.port=80 -jar /app/target/${CONFIG.service.jar}"
+    echo "$EXEC"
+    sh "$EXEC"
+}
+
+// Jenkins pipeline stages
+// closures = [
+//    GITCLONE: { -> },
+//    GENERATEDOCKERFILE: { -> },
+//    BUILDIMAGE: { -> },
+//    DOCKERRUN: { -> } 
+//]
+def execute(CONFIG, base_branch, closures=[:]) {
+
+    stage('source code check out') {
+        
+        println "loading ${sharedLibsPath}/gitUtil.groovy..."
+        def gitUtils = load "${sharedLibsPath}/gitUtil.groovy"
+
+        println closures.GITCLONE == null
+        COMMIT_SHA = closures.GITCLONE == null ? gitUtils.clone(CONFIG.git, base_branch) : closures.GITCLONE()
+    }
+
+    stage("docker image build") {
+
+        def projDir = 'projdir'
+        if (closures.BUILDJAR != null)
+            projDir = closures.BUILDJAR()
+        else
+            projDir = buildJar(projDir)
+
+        if (closures.GENERATEDOCKERFILE != null)
+            projDir = closures.GENERATEDOCKERFILE()
+        else
+            projDir = generateDockerfile(CONFIG.service)
+
+        if (closures.BUILDIMAGE != null)
+            imageUri = closures.BUILDIMAGE()
+        else
+            imageUri = buildDockerImage(CONFIG.service, CONFIG.docker, base_branch, projDir)
+        
+        if (closures.CLEANJAR != null)
+            closures.CLEANJAR()
+        else
+            cleanJar(projDir)
+    }
+
+    stage("run docker container") {
+
+        if(closures.DOCKERRUN != null){
+            closures.DOCKERRUN()
+            return
+        }
+
+        dockerRun(CONFIG, imageUri)        
+    }
+}
+
+return this

+ 101 - 0
sharedLibs/stages.docker.nodejs.groovy

@@ -0,0 +1,101 @@
+sharedLibsPath = "${env.WORKSPACE}/sharedLibs"
+backup_destination = '/var/jenkins_home/backups'
+backup_targetFile = 'dockerImage.tar'
+
+// 获取镜像URL
+generateFullImageUri = {Map DOCKER, String base_branch, String BUILD_NUMBER ->
+    return "${DOCKER.registry}/${DOCKER.image}:${base_branch}-${BUILD_NUMBER}"
+}
+
+// 获取镜像名
+generateImageName = {Map DOCKER, String base_branch, String BUILD_NUMBER ->
+    return "${DOCKER.image}:${base_branch}-${BUILD_NUMBER}"
+}
+
+def generateImageName(String imageName, String base_branch, String BUILD_NUMBER) {
+    return "${imageName}:${base_branch}-${BUILD_NUMBER}"
+}
+
+def generateDockerfile(Object SERVICE, String pathOfDockerfile='projdir') {
+    
+    def module = SERVICE.module == null ? '' : (SERVICE.module[-1] == '/' ? SERVICE.module : SERVICE.module + '/')
+    echo "-----> Generating Dockerfile: ${pathOfDockerfile}/Dockerfile ..."
+    sh """cat > ${pathOfDockerfile}/Dockerfile<<EOF
+FROM node:14-slim as BUILD
+LABEL stage=STATICRES-BUILD
+
+COPY ./admin /admin
+WORKDIR /admin
+RUN npm config set registry https://registry.npm.taobao.org && npm install && npm run build
+
+FROM nginx:1.23-alpine as FINAL
+
+# configs for default
+COPY ./default.conf /etc/nginx/conf.d
+COPY ./html /usr/share/nginx/html
+
+# configs for partner.hobbystocks.cn
+COPY ./partner.hobbystocks.cn.conf /etc/nginx/conf.d
+COPY --from=BUILD /admin/dist /usr/share/nginx/partner/admin
+EOF
+"""
+    return pathOfDockerfile
+}
+
+def buildDockerImage(Object SERVICE, Object DOCKER, String base_branch, projDir='projdir', String filter2Remove=null) {
+
+    def imageUri = generateFullImageUri(DOCKER, base_branch, BUILD_NUMBER)
+    echo "-----> Building docker image: ${imageUri} ..."
+    dir(projDir) {
+        docker.withRegistry("http://${DOCKER.registry}", "${DOCKER.push_credentialId}") {
+            def imageName = generateImageName(DOCKER, base_branch, BUILD_NUMBER)
+            def image = docker.build(imageName)
+            image.push()
+            sh "docker rmi ${imageName}"
+            if(filter2Remove != null)
+                sh "docker image prune --filter label=${filter2Remove} -f"
+        }
+    }
+    return imageUri
+}
+
+def cleanDockerImage(String dockerImgName) {
+    sh "docker rmi ${dockerImgName}"
+}
+
+
+// Jenkins pipeline stages
+def execute(CONFIG, base_branch, closures=[:]) {
+
+    stage('source code check out') {
+        
+        println "loading ${sharedLibsPath}/gitUtil.groovy..."
+        def gitUtils = load "${sharedLibsPath}/gitUtil.groovy"
+
+        COMMIT_SHA = closures.GITCLONE == null ? gitUtils.clone(CONFIG.git, base_branch) : closures.GITCLONE()
+    }
+
+    stage("docker image build") {
+
+        def projDir = 'projdir'
+        if (closures.GENERATEDOCKERFILE != null)
+            projDir = closures.GENERATEDOCKERFILE()
+        else
+            projDir = generateDockerfile(CONFIG.service) //default
+
+        if (closures.BUILDIMAGE != null)
+            imageUri = closures.BUILDIMAGE()
+        else
+            imageUri = buildDockerImage(CONFIG.service, CONFIG.docker, base_branch, projDir, 'stage=STATICRES-BUILD')
+    }
+
+    stage("run docker container") {
+
+        if(closures.DOCKERRUN != null){
+            closures.DOCKERRUN()
+            return
+        }
+    }
+}
+
+return this    

+ 113 - 0
sharedLibs/stages.groovy

@@ -0,0 +1,113 @@
+// can be called locally, without "def"
+myUtil = { -> 
+    println "hello myUtil"
+}
+
+sharedLibsPath = "${env.WORKSPACE}/sharedLibs"
+
+def applyService(SERVICE, K3S, String base_branch) {
+
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+
+    kvs = [:]
+    kvs.put('<service_name>', SERVICE.name)
+    kvs.put('<service_version>', SERVICE.version)
+    kvs.put('<release>', base_branch)
+    k3sUtils.generateService(kvs)
+    k3sUtils.applyService(K3S)
+}
+
+// 默认Java项目K8s部署(带configmap_配置目录)
+def deployWithConfigmaps(SERVICE, K3S, String base_branch, String[] args, configmapEnv="./configmap-env.ini", configmapConf="./configmap") {
+
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+
+    def configmap_env_name = k3sUtils.applyConfigMapEnv(SERVICE, K3S, configmapEnv)
+    def configmap_conf_name = k3sUtils.applyConfigMapConfig(SERVICE, K3S, configmapConf)
+    def java_args = '['
+    args.each{item -> java_args += "\"$item\","}
+    java_args = java_args[0..-2] + ']'
+
+    kvs = [:]
+    kvs.put('<service_name>', SERVICE.name)
+    kvs.put('<service_version>', SERVICE.version)
+    kvs.put('<release>', base_branch)
+    kvs.put('<COMMIT_SHA>', COMMIT_SHA)
+    kvs.put('<imagePullSecret>', K3S.pull_secretId)
+    kvs.put('<docker_image>', imageUri)
+    kvs.put('<java_args>', java_args)
+    if (SERVICE.health != null)
+        kvs.put('<service_health>', SERVICE.health)
+    kvs.put('<configmap_env_name>', configmap_env_name)
+    kvs.put('<configmap_conf_name>', configmap_conf_name)
+    k3sUtils.generateDeployment(kvs, 'deployment.yaml')
+    k3sUtils.applyDeployment(K3S)
+}
+
+// 默认Java项目K8s部署(仅配置configmap_Environment)
+def deployWithConfigmapEnvOnly(SERVICE, K3S, String base_branch, String[] args, configmapEnv="./configmap-env.ini"){
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+    
+    def configmap_env_name = k3sUtils.applyConfigMapEnv(SERVICE, K3S, configmapEnv)
+    print "configmap配置信息: ${configmap_env_name}"
+
+    def java_args = '['
+    args.each{item -> java_args += "\"$item\","}
+    java_args = java_args[0..-2] + ']'
+
+    kvs = [:]
+    kvs.put('<service_name>', SERVICE.name)
+    kvs.put('<service_version>', SERVICE.version)
+    kvs.put('<release>', base_branch)
+    kvs.put('<COMMIT_SHA>', COMMIT_SHA)
+    kvs.put('<imagePullSecret>', K3S.pull_secretId)
+    kvs.put('<docker_image>', imageUri)
+    kvs.put('<java_args>', java_args)
+    if (SERVICE.health != null)
+        kvs.put('<service_health>', SERVICE.health)
+    kvs.put('<configmap_env_name>', configmap_env_name)
+    k3sUtils.generateDeployment(kvs, 'deployment.yaml')
+    k3sUtils.applyDeployment(K3S)
+}
+
+// Jenkins pipeline stages
+def execute(CONFIG, base_branch, closures=[:]) {
+
+    stage('source code check out') {
+        
+        println "loading ${sharedLibsPath}/gitUtil.groovy..."
+        def gitUtils = load "${sharedLibsPath}/gitUtil.groovy"
+
+        COMMIT_SHA = closures.git != null ? closures.GITCLONE() : gitUtils.clone(CONFIG.git, base_branch)
+    }
+
+    stage("docker image build") {
+
+        println "loading ${sharedLibsPath}/imageBuilder.groovy..."
+        def imageBuilder = load "${sharedLibsPath}/imageBuilder.groovy"
+        
+        if (closures.BUILDIMAGE != null) {
+            imageUri = closures.BUILDIMAGE()
+            return
+        }
+
+        imageBuilder.buildJar()
+        imageUri = imageBuilder.buildDockerImage(CONFIG.service, CONFIG.docker, base_branch)
+        imageBuilder.cleanJar()
+    }
+
+    stage("apply K8S ConfigMaps && Deployment") {
+        
+        if (closures.K3SDEPLOY != null) {
+            closures.K3SDEPLOY()
+            return
+        }
+
+        deployWithConfigmapEnvOnly(CONFIG.service, CONFIG.k3s, base_branch, 'dev')
+    }
+}
+
+return this    

+ 555 - 0
sharedLibs/stages.k8s.groovy

@@ -0,0 +1,555 @@
+// can be called locally, without "def"
+myUtil = { -> 
+    println "hello myUtil"
+}
+
+sharedLibsPath = "${env.WORKSPACE}/sharedLibs"
+backup_destination = '/var/jenkins_home/backups'
+backup_targetFile = 'javaDockerImage.tar'
+BACKUP_MAX = 3
+
+// 获取镜像URL
+generateFullImageUri = {Map DOCKER, String base_branch, String BUILD_NUMBER ->
+    return "${DOCKER.registry}/${DOCKER.image}:${base_branch}-${BUILD_NUMBER}"
+}
+
+// 获取镜像名
+generateImageName = {Map DOCKER, String base_branch, String BUILD_NUMBER ->
+    return "${DOCKER.image}:${base_branch}-${BUILD_NUMBER}"
+}
+
+def processString(String content) {
+    return content
+}
+def buildJar(Object SERVICE, String path='projdir') {
+    
+    echo "-----> Building project package ..."
+    dir(path) {
+        if (SERVICE.module != null && SERVICE.module.trim())
+            sh "$MAVEN_HOME/bin/mvn clean package -pl \"${SERVICE.module}\" -am -Dmaven.test.skip=true"
+        else
+            sh "$MAVEN_HOME/bin/mvn clean package -Dmaven.test.skip=true"
+    }
+    return path
+}
+
+@Deprecated
+def backup_jar(Object SERVICE, String srcPath, String workingPath='projdir') {
+
+    echo "-----> Backup project artifact ..."
+    def module = SERVICE.module == null ? '' : (SERVICE.module[-1] == '/' ? SERVICE.module : SERVICE.module + '/')
+    path = "${backup_destination}/${JOB_NAME}/${BUILD_NUMBER}/target"
+    sh """if [ -d ${path} ];
+    then
+        echo \"${path} already exists\"
+    else
+        mkdir -p ${path}
+    fi
+    cp ${workingPath}/${module}${srcPath} ${path}
+    """
+}
+
+def cleanDockerImage(String dockerImgName) {
+    sh "docker rmi ${dockerImgName}"
+}
+
+def backupDockerImage(String dockerImgName) {
+
+    echo "-----> Backup artifact(docker image : ${dockerImgName}) ..."
+    path = "${backup_destination}/${JOB_NAME}/${BUILD_NUMBER}"
+    // docker save ${dockerImgName} | gzip > ${backup_targetFile}.tar.gz
+    sh """
+    docker save ${dockerImgName} -o ${backup_targetFile}
+    if [ -d ${path} ];
+    then
+        echo \"${path} already exists\"
+    else
+        mkdir -p ${path}
+        mv ${backup_targetFile} ${path}
+    fi
+    """
+}
+
+def backupK8sConfigs(String[] files2backup) {
+
+    path = "${backup_destination}/${JOB_NAME}/${BUILD_NUMBER}/k8s"
+    sh """if [ -d ${path} ];
+    then
+        echo \"${path} already exists!\"
+    else
+        mkdir -p ${path}
+    fi
+    """
+
+    for(file in files2backup) {
+        sh "cp -r ${file} ${path}"
+    }
+}
+
+def cleanJar(String path='projdir') {
+    echo "-----> Cleaning project package ..."
+    dir(path){
+        sh '$MAVEN_HOME/bin/mvn clean'
+    }
+    return path
+}
+
+def generateDockerfile(Object SERVICE, String pathOfDockerfile='projdir') {
+    
+    def module = SERVICE.module == null ? '' : (SERVICE.module[-1] == '/' ? SERVICE.module : SERVICE.module + '/')
+    echo "-----> Generating Dockerfile: ${pathOfDockerfile}/Dockerfile ..."
+    sh """cat > ${pathOfDockerfile}/Dockerfile<<EOF
+FROM openjdk:17-alpine as final
+ADD ${module}target/${SERVICE.jar} /app/target/${SERVICE.jar}
+EOF
+"""
+    return pathOfDockerfile
+}
+
+def buildDockerImage(Object SERVICE, Object DOCKER, String base_branch, projDir='projdir', String filter2Remove=null) {
+
+    def imageUri = generateFullImageUri(DOCKER, base_branch, BUILD_NUMBER)
+    echo "-----> Building docker image: ${imageUri} ..."
+    dir(projDir) {
+        docker.withRegistry("http://${DOCKER.registry}", "${DOCKER.push_credentialId}") {
+            def imageName = generateImageName(DOCKER, base_branch, BUILD_NUMBER)
+            def image = docker.build(imageName)
+            image.push()
+            sh "docker rmi ${imageName}"
+            // sh "docker rmi ${imageUri}"
+            if(filter2Remove != null)
+                sh "docker image prune --filter label=${filter2Remove} -f"
+        }
+    }
+    return imageUri
+}
+
+def restoreDockerImage(Object DOCKER, String workspace, String base_branch, String rollbackBuildNumber) {
+
+    dir(workspace) {
+
+        imageUri = generateFullImageUri(DOCKER, base_branch, rollbackBuildNumber)
+        // gunzip ${backup_targetFile}.tar.gz | docker load
+        sh "docker load -i ${backup_targetFile}"
+        // in case, docker image doesn't exist in remote registry
+        docker.withRegistry("http://${DOCKER.registry}", "${DOCKER.push_credentialId}") {
+            def image = docker.image(imageUri)
+            image.push()
+        }
+    }
+    return imageUri
+}
+
+def deployWithConfigmapEnvOnly(SERVICE, K3S, String base_branch, String[] args, configmapEnv="./configmap-env.ini", namespace='default') {
+
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+
+    def configmap_env_name = k3sUtils.applyConfigMapEnv(SERVICE, K3S, configmapEnv, namespace)
+    def java_args = '['
+        args.each{item -> java_args += "\"$item\","}
+    java_args = java_args[0..-2] + ']'
+
+    kvs = [:]
+    kvs.put('<service_name>', SERVICE.name)
+    kvs.put('<service_version>', SERVICE.version)
+    kvs.put('<release>', base_branch)
+    kvs.put('<COMMIT_SHA>', COMMIT_SHA)
+    kvs.put('<imagePullSecret>', K3S.pull_secretId)
+    kvs.put('<docker_image>', imageUri)
+    kvs.put('<java_args>', java_args)
+    if (SERVICE.health != null)
+        kvs.put('<service_health>', SERVICE.health)
+    kvs.put('<configmap_env_name>', configmap_env_name)
+    k3sUtils.generateDeployment(kvs, 'deployment.yaml')
+    k3sUtils.applyDeployment(K3S, 'deployment.yaml')
+
+    k3sUtils.generateService(kvs, 'service.yaml')
+    k3sUtils.applyService(K3S, 'service.yaml')
+}
+
+def deployWithConfigmapEnvOnly_rollback(SERVICE, K3S, String rollbackBuildNumber, String[] args, configmapEnv="./configmap-env.ini", namespace='default') {
+
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+
+    dir("${backup_destination}/${JOB_NAME}/${rollbackBuildNumber}/k8s") {
+
+        def configmap_env_name = k3sUtils.applyConfigMapEnv(SERVICE, K3S, configmapEnv, namespace)
+        def java_args = '['
+            args.each{item -> java_args += "\"$item\","}
+        java_args = java_args[0..-2] + ']'
+
+        kvs = [:]
+        kvs.put('<service_name>', SERVICE.name)
+        kvs.put('<service_version>', SERVICE.version)
+        kvs.put('<release>', BASE_BRANCH)
+        kvs.put('<COMMIT_SHA>', COMMIT_SHA)
+        kvs.put('<imagePullSecret>', K3S.pull_secretId)
+        kvs.put('<docker_image>', imageUri)
+        kvs.put('<java_args>', java_args)
+        if (SERVICE.health != null)
+            kvs.put('<service_health>', SERVICE.health)
+        kvs.put('<configmap_env_name>', configmap_env_name)
+        k3sUtils.generateDeployment(kvs, 'deployment.yaml')
+        k3sUtils.applyDeployment(K3S, 'deployment.yaml')
+
+        k3sUtils.generateService(kvs, 'service.yaml')
+        k3sUtils.applyService(K3S, 'service.yaml')
+    }
+}
+
+def deployWithConfigmapEnvOnly2nd(SERVICE, K3S, String base_branch, String[] args, configmapEnv="./configmap-env.ini", namespace='default') {
+
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+
+    def configmap_env_name = k3sUtils.applyConfigMapEnv(SERVICE, K3S, configmapEnv, namespace)
+    def java_args = '['
+        args.each{item -> java_args += "\"$item\","}
+    java_args = java_args[0..-2] + ']'
+
+    kvs = [:]
+    kvs.put('<service_name>', SERVICE.name)
+    kvs.put('<service_version>', SERVICE.version)
+    kvs.put('<release>', base_branch)
+    kvs.put('<COMMIT_SHA>', COMMIT_SHA)
+    kvs.put('<imagePullSecret>', K3S.pull_secretId)
+    kvs.put('<docker_image>', imageUri)
+    kvs.put('<java_args>', java_args)
+    if (SERVICE.health != null)
+        kvs.put('<service_health>', SERVICE.health)
+    kvs.put('<configmap_env_name>', configmap_env_name)
+    k3sUtils.generateDeployment(kvs, 'deployment2.yaml')
+    k3sUtils.applyDeployment(K3S, 'deployment2.yaml')
+
+    k3sUtils.generateService(kvs, 'service2.yaml')
+    k3sUtils.applyService(K3S, 'service2.yaml')
+}
+
+def deployWithConfigmapEnvOnly2nd_rollback(SERVICE, K3S, String rollbackBuildNumber, String[] args, configmapEnv="./configmap-env.ini", namespace='default') {
+
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+
+    dir("${backup_destination}/${JOB_NAME}/${rollbackBuildNumber}/k8s") {
+
+        def configmap_env_name = k3sUtils.applyConfigMapEnv(SERVICE, K3S, configmapEnv, namespace)
+        def java_args = '['
+            args.each{item -> java_args += "\"$item\","}
+        java_args = java_args[0..-2] + ']'
+
+        kvs = [:]
+        kvs.put('<service_name>', SERVICE.name)
+        kvs.put('<service_version>', SERVICE.version)
+        kvs.put('<release>', BASE_BRANCH)
+        kvs.put('<COMMIT_SHA>', COMMIT_SHA)
+        kvs.put('<imagePullSecret>', K3S.pull_secretId)
+        kvs.put('<docker_image>', imageUri)
+        kvs.put('<java_args>', java_args)
+        if (SERVICE.health != null)
+            kvs.put('<service_health>', SERVICE.health)
+        kvs.put('<configmap_env_name>', configmap_env_name)
+        k3sUtils.generateDeployment(kvs, 'deployment2.yaml')
+        k3sUtils.applyDeployment(K3S, 'deployment2.yaml')
+
+        k3sUtils.generateService(kvs, 'service2.yaml')
+        k3sUtils.applyService(K3S, 'service2.yaml')
+    }
+}
+
+def deployWithConfigmaps_rollback(SERVICE, K3S, String rollbackBuildNumber, String[] args, configmapEnv="./configmap-env.ini", configmapConf="./configmap") {
+    
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+
+    dir("${backup_destination}/${JOB_NAME}/${rollbackBuildNumber}/k8s") {
+        def configmap_env_name = k3sUtils.applyConfigMapEnv(SERVICE, K3S, configmapEnv)
+        def configmap_conf_name = k3sUtils.applyConfigMapConfig(SERVICE, K3S, configmapConf)
+        def java_args = '['
+            args.each{item -> java_args += "\"$item\","}
+        java_args = java_args[0..-2] + ']'
+
+        kvs = [:]
+        kvs.put('<service_name>', SERVICE.name)
+        kvs.put('<service_version>', SERVICE.version)
+        kvs.put('<release>', BASE_BRANCH)
+        kvs.put('<COMMIT_SHA>', COMMIT_SHA)
+        kvs.put('<imagePullSecret>', K3S.pull_secretId)
+        kvs.put('<docker_image>', imageUri)
+        kvs.put('<java_args>', java_args)
+        if (SERVICE.health != null)
+            kvs.put('<service_health>', SERVICE.health)
+        kvs.put('<configmap_env_name>', configmap_env_name)
+        kvs.put('<configmap_conf_name>', configmap_conf_name)
+        k3sUtils.generateDeployment(kvs, 'deployment.yaml')
+        k3sUtils.applyDeployment(K3S, 'deployment.yaml')
+
+        k3sUtils.generateService(kvs, 'service.yaml')
+        k3sUtils.applyService(K3S, 'service.yaml')
+    }
+}
+
+def deployWithConfigmaps(SERVICE, K3S, String base_branch, String[] args, configmapEnv="./configmap-env.ini", configmapConf="./configmap") {
+
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+
+    def configmap_env_name = k3sUtils.applyConfigMapEnv(SERVICE, K3S, configmapEnv)
+    def configmap_conf_name = k3sUtils.applyConfigMapConfig(SERVICE, K3S, configmapConf)
+    def java_args = '['
+        args.each{item -> java_args += "\"$item\","}
+    java_args = java_args[0..-2] + ']'
+
+    kvs = [:]
+    kvs.put('<service_name>', SERVICE.name)
+    kvs.put('<service_version>', SERVICE.version)
+    kvs.put('<release>', base_branch)
+    kvs.put('<COMMIT_SHA>', COMMIT_SHA)
+    kvs.put('<imagePullSecret>', K3S.pull_secretId)
+    kvs.put('<docker_image>', imageUri)
+    kvs.put('<java_args>', java_args)
+    if (SERVICE.health != null)
+        kvs.put('<service_health>', SERVICE.health)
+    kvs.put('<configmap_env_name>', configmap_env_name)
+    kvs.put('<configmap_conf_name>', configmap_conf_name)
+    k3sUtils.generateDeployment(kvs, 'deployment.yaml')
+    k3sUtils.applyDeployment(K3S, 'deployment.yaml')
+
+    k3sUtils.generateService(kvs, 'service.yaml')
+    k3sUtils.applyService(K3S, 'service.yaml')
+}
+
+def deployWithConfigmaps2nd(SERVICE, K3S, String base_branch, String[] args, configmapEnv="./configmap-env.ini", configmapConf="./configmap") {
+
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+
+    def configmap_env_name = k3sUtils.applyConfigMapEnv(SERVICE, K3S, configmapEnv)
+    def configmap_conf_name = k3sUtils.applyConfigMapConfig(SERVICE, K3S, configmapConf)
+    def java_args = '['
+    args.each{item -> java_args += "\"$item\","}
+    java_args = java_args[0..-2] + ']'
+
+    kvs = [:]
+    kvs.put('<service_name>', SERVICE.name)
+    kvs.put('<service_version>', SERVICE.version)
+    kvs.put('<release>', base_branch)
+    kvs.put('<COMMIT_SHA>', COMMIT_SHA)
+    kvs.put('<imagePullSecret>', K3S.pull_secretId)
+    kvs.put('<docker_image>', imageUri)
+    kvs.put('<java_args>', java_args)
+    if (SERVICE.health != null)
+        kvs.put('<service_health>', SERVICE.health)
+    kvs.put('<configmap_env_name>', configmap_env_name)
+    kvs.put('<configmap_conf_name>', configmap_conf_name)
+    k3sUtils.generateDeployment(kvs, 'deployment2.yaml')
+    k3sUtils.applyDeployment(K3S, 'deployment2.yaml')
+
+    k3sUtils.generateService(kvs, 'service2.yaml')
+    k3sUtils.applyService(K3S, 'service2.yaml')
+}
+
+// Jenkins pipeline stages
+def execute(CONFIG, base_branch, closures=[:]) {                    //配置文件,当前分支,自定义闭包
+
+    stage('source code check out') {
+        
+        println "loading ${sharedLibsPath}/gitUtil.groovy..."     //打印信息共享库路径,变量路径,存储加载的录像本身地址,脚本名称
+        def gitUtils = load "${sharedLibsPath}/gitUtil.groovy"    //load方法加载对象,赋值给gitUtils
+
+        COMMIT_SHA = closures.GITCLONE == null ? gitUtils.clone(CONFIG.git, base_branch) : closures.GITCLONE()  //定义变量,为空则调用git配置中git仓库信息branch当前分支,不空调用自定义闭包
+    }
+
+    stage("docker image build") {
+
+        def projDir = 'projdir'                                 //定义变量,项目目录
+        if (closures.BUILDJAR != null)                          //检查是否自定义闭包,有就使用,没有就使用默认方法
+            projDir = closures.BUILDJAR()
+        else
+            projDir = buildJar(CONFIG.service, projDir) //default
+
+        if (closures.BACKUP != null)
+            closures.BACKUP()
+
+        if (closures.GENERATEDOCKERFILE != null)
+            projDir = closures.GENERATEDOCKERFILE()
+        else
+            projDir = generateDockerfile(CONFIG.service) //default
+
+        if (closures.BUILDIMAGE != null)
+            imageUri = closures.BUILDIMAGE()
+        else
+            imageUri = buildDockerImage(CONFIG.service, CONFIG.docker, base_branch, projDir)
+
+        if (closures.CLEANJAR != null)
+            closures.CLEANJAR()
+        else
+            cleanJar(projDir)
+    }
+    stage("notify when build") {
+        
+        //notifywhenbuild(base_branch, CONFIG.service.name)
+    }
+    stage("apply K8S ConfigMaps && Deployment") {
+        
+        if (closures.K3SDEPLOY != null) {
+            closures.K3SDEPLOY()
+            return
+        }
+    }
+}
+
+def upgrade(Object CONFIG, String base_branch, String[] k8sBackupFiles, closures=[:]) {
+    
+    stage('source code check out') {
+        
+        println "loading ${sharedLibsPath}/gitUtil.groovy..."
+        def gitUtils = load "${sharedLibsPath}/gitUtil.groovy"
+
+        COMMIT_SHA = closures.GITCLONE == null ? gitUtils.clone(CONFIG.git, base_branch) : closures.GITCLONE()
+
+        if (closures.POST_GITCLONE != null)
+            closures.POST_GITCLONE()
+    }
+
+    stage("docker image build") {
+
+        def projDir = 'projdir'
+        if (closures.BUILDJAR != null)
+            projDir = closures.BUILDJAR()
+        else
+            projDir = buildJar(CONFIG.service, projDir) //default
+
+        if (closures.GENERATEDOCKERFILE != null)
+            projDir = closures.GENERATEDOCKERFILE()
+        else
+            projDir = generateDockerfile(CONFIG.service) //default
+
+        if (closures.BUILDIMAGE != null)
+            imageUri = closures.BUILDIMAGE()
+        else
+            imageUri = buildDockerImage(CONFIG.service, CONFIG.docker, base_branch, projDir)
+
+        if (closures.CLEANJAR != null)
+            closures.CLEANJAR()
+        else
+            cleanJar(projDir)
+
+        // stage("Docker Image backup") {
+        if (k8sBackupFiles != null && k8sBackupFiles.size() > 0) {
+            backupDockerImage(imageUri)
+            envFile = new File("${backup_destination}/${JOB_NAME}/${BUILD_NUMBER}/env")
+            envFile << "${COMMIT_SHA}\n"
+            envFile << "${base_branch}\n"
+        }
+        // }
+
+        cleanDockerImage(imageUri)
+    }
+
+    stage("apply K8S ConfigMaps && Deployment") {
+        
+        // stage("K8s configs backup") {
+        if (k8sBackupFiles != null && k8sBackupFiles.size() > 0)
+            backupK8sConfigs(k8sBackupFiles)
+        // }
+
+        if (closures.K3SDEPLOY != null) {
+            closures.K3SDEPLOY()
+            return
+        }
+    }
+    stage("notify when build") {
+        if (closures.BUILDINFO != null) {
+            def buildInfo = closures.ALLCHANGES()
+            if (buildInfo.length() > 800) {
+                buildInfo = buildInfo.substring(buildInfo.length() - 800)
+            }
+            def title = closures.BUILDINFO()+"正在发布"
+            def content = "变更:"+buildInfo+"\\n"+
+                        "分支:"+base_branch+"\\n"+
+                        "版本:"+BUILD_NUMBER+"\\n"
+            sendGroupNotification(title, content)
+        }
+    }
+    stage("Post Execution Script") {
+
+        print "Clear older backup version"
+        // 清理旧的备份
+        dir("${backup_destination}/${JOB_NAME}") {
+            sh """
+            save_file=`ls -ltr | tail -${BACKUP_MAX} | awk '{print \$NF}'`
+            ls | grep -v "\$save_file" | xargs rm -rf
+            """
+        }
+    }
+    
+}
+
+
+def rollback(CONFIG, rollbackBuildNumber, closures=[:]) {
+    // 1. restore specified version
+    path = "${backup_destination}/${JOB_NAME}/${rollbackBuildNumber}"
+    if (fileExists(path)) {
+
+        stage('source code check out') {
+            print("souce code check out. Skipped!")
+        }
+
+
+        envFile = new File("${backup_destination}/${JOB_NAME}/${rollbackBuildNumber}/env")
+        def lines = envFile.readLines()
+        COMMIT_SHA = lines[0]
+        BASE_BRANCH = lines[1]
+
+        // 2. docker image build
+        stage("docker image build") {
+            print("restore docker image from rollbackNo. #${rollbackBuildNumber}")
+            imageUri = restoreDockerImage(CONFIG.docker, path, BASE_BRANCH, rollbackBuildNumber)
+            cleanDockerImage(imageUri)
+        }
+
+        // 3. k8s configmap && deployment apply
+        stage("apply K8S ConfigMaps && Deployment") {
+            if (closures.K3SDEPLOY != null)
+                closures.K3SDEPLOY()
+        }
+
+        stage("Post Execution Script") {
+
+            print "Clear rollback workspace"
+            // 清理rollback工作目录
+            dir("${backup_destination}/${JOB_NAME}") {
+                sh "rm -rf *@tmp"
+            }
+        }
+    }
+    
+}
+
+
+def sendGroupNotification(String title, String content) {
+
+    WECHAT_ROBOT_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=92059925-97db-4830-8438-1514a246a143"
+    stage('Send Message') {
+       
+                        // 构建要发送的企业微信消息
+                        def message = """
+                        {
+                            "msgtype": "text",
+                            "text": {
+                                "content": "【${title}】\n${content}"
+                            }
+                        }
+                        """
+                        // 发送消息到企业微信
+                        sh """
+                        curl -X POST -H 'Content-Type: application/json' -d '${message}' ${WECHAT_ROBOT_URL}
+                        """
+    }   
+
+}    
+
+        
+return this

+ 326 - 0
sharedLibs/stages.k8s.python.groovy

@@ -0,0 +1,326 @@
+// -------------
+// python on K8S
+sharedLibsPath = "${env.WORKSPACE}/sharedLibs"
+backup_destination = '/var/jenkins_home/backups'
+backup_targetFile = 'dockerImage.tar'
+backup_num = 3
+
+// 获取镜像URL
+generateFullImageUri = {Map DOCKER, String base_branch, String BUILD_NUMBER ->
+    return "${DOCKER.registry}/${DOCKER.image}:${base_branch}-${BUILD_NUMBER}"
+}
+
+// 获取镜像名
+generateImageName = {Map DOCKER, String base_branch, String BUILD_NUMBER ->
+    return "${DOCKER.image}:${base_branch}-${BUILD_NUMBER}"
+}
+
+def cleanDockerImage(String dockerImgName) {
+    sh "docker rmi ${dockerImgName}"
+}
+
+def backupDockerImage(String dockerImgName) {
+
+    echo "-----> Backup artifact(docker image : ${dockerImgName}) ..."
+    path = "${backup_destination}/${JOB_NAME}/${BUILD_NUMBER}"
+
+    sh """
+    docker save ${dockerImgName} -o ${backup_targetFile}
+    if [ -d ${path} ];
+    then
+        echo \"${path} already exists\"
+    else
+        mkdir -p ${path}
+    fi
+    mv ${backup_targetFile} ${path}
+    """
+}
+
+def backupK8sConfigs(String[] files2backup) {
+
+    path = "${backup_destination}/${JOB_NAME}/${BUILD_NUMBER}/k8s"
+    sh """if [ -d ${path} ];
+    then
+        echo \"${path} already exists!\"
+    else
+        mkdir -p ${path}
+    fi
+    """
+
+    for(file in files2backup) {
+        sh "cp -r ${file} ${path}"
+    }
+}
+
+def generateDockerfile(Object SERVICE, String pathOfDockerfile='projdir') {
+    
+    def module = SERVICE.module == null ? '' : (SERVICE.module[-1] == '/' ? SERVICE.module : SERVICE.module + '/')
+    echo "-----> Generating Dockerfile: ${pathOfDockerfile}/Dockerfile ..."
+    sh """cat > ${pathOfDockerfile}/Dockerfile<<EOF
+FROM python:3.10-slim as final
+
+COPY ./requirements.txt /app
+COPY ./spider.py /app
+ 
+RUN pip install --upgrade pip
+RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
+RUN pip install --no-cache-dir -r requirements.txt
+EOF
+"""
+    return pathOfDockerfile
+}
+
+def buildDockerImage(Object SERVICE, Object DOCKER, String base_branch, projDir='projdir', String filter2Remove=null) {
+
+    def imageUri = generateFullImageUri(DOCKER, base_branch, BUILD_NUMBER)
+    echo "-----> Building docker image: ${imageUri} ..."
+    dir(projDir) {
+        docker.withRegistry("http://${DOCKER.registry}", "${DOCKER.push_credentialId}") {
+            def imageName = generateImageName(DOCKER, base_branch, BUILD_NUMBER)
+            def image = docker.build(imageName)
+            image.push()
+            sh "docker rmi ${imageName}"
+            if(filter2Remove != null)
+                sh "docker image prune --filter label=${filter2Remove} -f"
+        }
+    }
+    return imageUri
+}
+
+def restoreDockerImage(Object DOCKER, String workspace, String base_branch, String rollbackBuildNumber) {
+
+    dir(workspace) {
+
+        imageUri = generateFullImageUri(DOCKER, base_branch, rollbackBuildNumber)
+        // gunzip ${backup_targetFile}.tar.gz | docker load
+        sh "docker load -i ${backup_targetFile}"
+        // in case, docker image doesn't exist in remote registry
+        docker.withRegistry("http://${DOCKER.registry}", "${DOCKER.push_credentialId}") {
+            def image = docker.image(imageUri)
+            image.push()
+        }
+    }
+    return imageUri
+}
+
+def deployWithConfigmapEnvOnly(SERVICE, K3S, String base_branch, String[] args, configmapEnv="./configmap-env.ini", namespace='default') {
+
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+
+    def configmap_env_name = k3sUtils.applyConfigMapEnv(SERVICE, K3S, configmapEnv, namespace)
+    def python_args = '['
+        args.each{item -> python_args += "\"$item\","}
+    python_args = python_args[0..-2] + ']'
+
+    kvs = [:]
+    kvs.put('<service_name>', SERVICE.name)
+    kvs.put('<service_version>', SERVICE.version)
+    kvs.put('<release>', base_branch)
+    kvs.put('<COMMIT_SHA>', COMMIT_SHA)
+    kvs.put('<imagePullSecret>', K3S.pull_secretId)
+    kvs.put('<docker_image>', imageUri)
+    kvs.put('<python_args>', python_args)
+    if (SERVICE.health != null)
+        kvs.put('<service_health>', SERVICE.health)
+    kvs.put('<configmap_env_name>', configmap_env_name)
+    k3sUtils.generateDeployment(kvs, 'deployment.yaml')
+    k3sUtils.applyDeployment(K3S, 'deployment.yaml')
+
+    k3sUtils.generateService(kvs, 'service.yaml')
+    k3sUtils.applyService(K3S, 'service.yaml')
+}
+
+def deployWithConfigmapEnvOnly_rollback(SERVICE, K3S, String rollbackBuildNumber, String[] args, configmapEnv="./configmap-env.ini", namespace='default') {
+
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+
+    dir("${backup_destination}/${JOB_NAME}/${rollbackBuildNumber}/k8s") {
+
+        def configmap_env_name = k3sUtils.applyConfigMapEnv(SERVICE, K3S, configmapEnv, namespace)
+        def java_args = '['
+            args.each{item -> java_args += "\"$item\","}
+        java_args = java_args[0..-2] + ']'
+
+        kvs = [:]
+        kvs.put('<service_name>', SERVICE.name)
+        kvs.put('<service_version>', SERVICE.version)
+        kvs.put('<release>', BASE_BRANCH)
+        kvs.put('<COMMIT_SHA>', COMMIT_SHA)
+        kvs.put('<imagePullSecret>', K3S.pull_secretId)
+        kvs.put('<docker_image>', imageUri)
+        kvs.put('<java_args>', java_args)
+        if (SERVICE.health != null)
+            kvs.put('<service_health>', SERVICE.health)
+        kvs.put('<configmap_env_name>', configmap_env_name)
+        k3sUtils.generateDeployment(kvs, 'deployment.yaml')
+        k3sUtils.applyDeployment(K3S, 'deployment.yaml')
+
+        k3sUtils.generateService(kvs, 'service.yaml')
+        k3sUtils.applyService(K3S, 'service.yaml')
+    }
+}
+
+def deployWithConfigmaps_rollback(SERVICE, K3S, String rollbackBuildNumber, String[] args, configmapEnv="./configmap-env.ini", configmapConf="./configmap") {
+    
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+
+    dir("${backup_destination}/${JOB_NAME}/${rollbackBuildNumber}/k8s") {
+        def configmap_env_name = k3sUtils.applyConfigMapEnv(SERVICE, K3S, configmapEnv)
+        def configmap_conf_name = k3sUtils.applyConfigMapConfig(SERVICE, K3S, configmapConf)
+        def java_args = '['
+            args.each{item -> java_args += "\"$item\","}
+        java_args = java_args[0..-2] + ']'
+
+        kvs = [:]
+        kvs.put('<service_name>', SERVICE.name)
+        kvs.put('<service_version>', SERVICE.version)
+        kvs.put('<release>', BASE_BRANCH)
+        kvs.put('<COMMIT_SHA>', COMMIT_SHA)
+        kvs.put('<imagePullSecret>', K3S.pull_secretId)
+        kvs.put('<docker_image>', imageUri)
+        kvs.put('<java_args>', java_args)
+        if (SERVICE.health != null)
+            kvs.put('<service_health>', SERVICE.health)
+        kvs.put('<configmap_env_name>', configmap_env_name)
+        kvs.put('<configmap_conf_name>', configmap_conf_name)
+        k3sUtils.generateDeployment(kvs, 'deployment.yaml')
+        k3sUtils.applyDeployment(K3S, 'deployment.yaml')
+
+        k3sUtils.generateService(kvs, 'service.yaml')
+        k3sUtils.applyService(K3S, 'service.yaml')
+    }
+}
+
+def deployWithConfigmaps(SERVICE, K3S, String base_branch, String[] args, configmapEnv="./configmap-env.ini", configmapConf="./configmap") {
+
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+
+    def configmap_env_name = k3sUtils.applyConfigMapEnv(SERVICE, K3S, configmapEnv)
+    def configmap_conf_name = k3sUtils.applyConfigMapConfig(SERVICE, K3S, configmapConf)
+    def java_args = '['
+        args.each{item -> java_args += "\"$item\","}
+    java_args = java_args[0..-2] + ']'
+
+    kvs = [:]
+    kvs.put('<service_name>', SERVICE.name)
+    kvs.put('<service_version>', SERVICE.version)
+    kvs.put('<release>', base_branch)
+    kvs.put('<COMMIT_SHA>', COMMIT_SHA)
+    kvs.put('<imagePullSecret>', K3S.pull_secretId)
+    kvs.put('<docker_image>', imageUri)
+    kvs.put('<java_args>', java_args)
+    if (SERVICE.health != null)
+        kvs.put('<service_health>', SERVICE.health)
+    kvs.put('<configmap_env_name>', configmap_env_name)
+    kvs.put('<configmap_conf_name>', configmap_conf_name)
+    k3sUtils.generateDeployment(kvs, 'deployment.yaml')
+    k3sUtils.applyDeployment(K3S, 'deployment.yaml')
+
+    k3sUtils.generateService(kvs, 'service.yaml')
+    k3sUtils.applyService(K3S, 'service.yaml')
+}
+
+// Jenkins pipeline stages
+def upgrade(Object CONFIG, String base_branch, String[] k8sBackupFiles, closures=[:]) {
+    
+    stage('Source code check out') {
+        println "loading ${sharedLibsPath}/gitUtil.groovy..."
+        def gitUtils = load "${sharedLibsPath}/gitUtil.groovy"
+
+        COMMIT_SHA = closures.GITCLONE == null ? gitUtils.clone(CONFIG.git, base_branch) : closures.GITCLONE()
+
+        if (closures.POST_GITCLONE != null)
+            closures.POST_GITCLONE()
+    }
+
+    stage("Docker image build") {
+        def projDir = 'projdir'
+
+        if (closures.GENERATEDOCKERFILE != null)
+            projDir = closures.GENERATEDOCKERFILE()
+        else
+            projDir = generateDockerfile(CONFIG.service) //default
+
+        if (closures.BUILDIMAGE != null)
+            imageUri = closures.BUILDIMAGE()
+        else
+            imageUri = buildDockerImage(CONFIG.service, CONFIG.docker, base_branch, projDir)
+
+        // stage("Docker Image backup") {
+        if (k8sBackupFiles != null && k8sBackupFiles.size() > 0) {
+            backupDockerImage(imageUri)
+            envFile = new File("${backup_destination}/${JOB_NAME}/${BUILD_NUMBER}/env")
+            envFile << "${COMMIT_SHA}\n"
+            envFile << "${base_branch}\n"
+        }
+        // }
+
+        cleanDockerImage(imageUri)
+    }
+
+    stage("Apply K8S ConfigMaps && Deployment") {
+        // stage("K8s configs backup") {
+        if (k8sBackupFiles != null && k8sBackupFiles.size() > 0)
+            backupK8sConfigs(k8sBackupFiles)
+        // }
+
+        if (closures.K3SDEPLOY != null) {
+            closures.K3SDEPLOY()
+            return
+        }
+    }
+
+    stage("Post Execution Script") {
+        print "Clear older backup version"
+        // 清理旧的备份
+        dir("${backup_destination}/${JOB_NAME}") {
+            sh """
+            save_file=`ls -ltr | tail -${backup_num} | awk '{print \$NF}'`
+            ls | grep -v "\$save_file" | xargs rm -rf
+            """
+        }
+    }
+}
+
+def rollback(CONFIG, rollbackBuildNumber, closures=[:]) {
+    // 1. restore specified version
+    path = "${backup_destination}/${JOB_NAME}/${rollbackBuildNumber}"
+    if (fileExists(path)) {
+
+        stage('Source code check out') {
+            print("souce code check out. Skipped!")
+        }
+
+        envFile = new File("${backup_destination}/${JOB_NAME}/${rollbackBuildNumber}/env")
+        def lines = envFile.readLines()
+        COMMIT_SHA = lines[0]
+        BASE_BRANCH = lines[1]
+
+        // 2. docker image build
+        stage("Docker image build") {
+            print("restore docker image from rollbackNo. #${rollbackBuildNumber}")
+            imageUri = restoreDockerImage(CONFIG.docker, path, BASE_BRANCH, rollbackBuildNumber)
+            cleanDockerImage(imageUri)
+        }
+
+        // 3. k8s configmap && deployment apply
+        stage("Apply K8S ConfigMaps && Deployment") {
+            if (closures.K3SDEPLOY != null)
+                closures.K3SDEPLOY()
+        }
+
+        stage("Post Execution Script") {
+            print "Clear rollback workspace"
+            // 清理rollback工作目录
+            dir("${backup_destination}/${JOB_NAME}") {
+                sh "rm -rf *@tmp"
+            }
+        }
+    }
+}
+
+return this

+ 269 - 0
sharedLibs/stages.nodejs.groovy

@@ -0,0 +1,269 @@
+sharedLibsPath = "${env.WORKSPACE}/sharedLibs"
+backup_destination = '/var/jenkins_home/backups'
+backup_targetFile = 'dockerImage.tar'
+
+// 获取镜像URL
+generateFullImageUri = {Map DOCKER, String base_branch, String BUILD_NUMBER ->
+    return "${DOCKER.registry}/${DOCKER.image}:${base_branch}-${BUILD_NUMBER}"
+}
+
+// 获取镜像名
+generateImageName = {Map DOCKER, String base_branch, String BUILD_NUMBER ->
+    return "${DOCKER.image}:${base_branch}-${BUILD_NUMBER}"
+}
+
+def generateImageName(String imageName, String base_branch, String BUILD_NUMBER) {
+    return "${imageName}:${base_branch}-${BUILD_NUMBER}"
+}
+
+def generateDockerfile(Object SERVICE, String pathOfDockerfile='projdir') {
+    
+    def module = SERVICE.module == null ? '' : (SERVICE.module[-1] == '/' ? SERVICE.module : SERVICE.module + '/')
+    echo "-----> Generating Dockerfile: ${pathOfDockerfile}/Dockerfile ..."
+    sh """cat > ${pathOfDockerfile}/Dockerfile<<EOF
+FROM node:14-slim as BUILD
+LABEL stage=STATICRES-BUILD
+
+COPY ./admin /admin
+WORKDIR /admin
+RUN npm config set registry https://registry.npm.taobao.org && npm install && npm run build
+
+FROM nginx:1.23-alpine as FINAL
+
+# configs for default
+COPY ./default.conf /etc/nginx/conf.d
+COPY ./html /usr/share/nginx/html
+
+# configs for partner.hobbystocks.cn
+COPY ./partner.hobbystocks.cn.conf /etc/nginx/conf.d
+COPY --from=BUILD /admin/dist /usr/share/nginx/partner/admin
+EOF
+"""
+    return pathOfDockerfile
+}
+
+def buildDockerImage(Object SERVICE, Object DOCKER, String base_branch, projDir='projdir', String filter2Remove=null) {
+
+    def imageUri = generateFullImageUri(DOCKER, base_branch, BUILD_NUMBER)
+    echo "-----> Building docker image: ${imageUri} ..."
+    dir(projDir) {
+        docker.withRegistry("http://${DOCKER.registry}", "${DOCKER.push_credentialId}") {
+            def imageName = generateImageName(DOCKER, base_branch, BUILD_NUMBER)
+            def image = docker.build(imageName)
+            image.push()
+            sh "docker rmi ${imageName}"
+            if(filter2Remove != null)
+                sh "docker image prune --filter label=${filter2Remove} -f"
+        }
+    }
+    return imageUri
+}
+
+def restoreDockerImage(Object DOCKER, String workspace, String base_branch, String rollbackBuildNumber) {
+
+    dir(workspace) {
+
+        imageUri = generateFullImageUri(DOCKER, base_branch, rollbackBuildNumber)
+        sh "docker load -i ${backup_targetFile}"
+        // in case, docker image doesn't exist in remote registry
+        docker.withRegistry("http://${DOCKER.registry}", "${DOCKER.push_credentialId}") {
+            def image = docker.image(imageUri)
+            image.push()
+        }
+    }
+    return imageUri
+}
+
+def cleanDockerImage(String dockerImgName) {
+    sh "docker rmi ${dockerImgName}"
+}
+
+def backupDockerImage(String dockerImgName) {
+
+    echo "-----> Backup artifact(docker image : ${dockerImgName}) ..."
+    path = "${backup_destination}/${JOB_NAME}/${BUILD_NUMBER}"
+    sh """
+    docker save ${dockerImgName} -o ${backup_targetFile}
+    if [ -d ${path} ];
+    then
+        echo \"${path} already exists\"
+    else
+        mkdir -p ${path}
+        mv ${backup_targetFile} ${path}
+    fi
+    """
+}
+
+def backupK8sConfigs(String[] files2backup) {
+
+    path = "${backup_destination}/${JOB_NAME}/${BUILD_NUMBER}/k8s"
+    sh """if [ -d ${path} ];
+    then
+        echo \"${path} already exists!\"
+    else
+        mkdir -p ${path}
+    fi
+    """
+
+    for(file in files2backup) {
+        sh "cp ${file} ${path}"
+    }
+}
+
+def applyDeployment(SERVICE, K3S, String base_branch) {
+
+    print "loading ${sharedLibsPath}/k3sUtil.groovy..."
+    def k3sUtils = load "${sharedLibsPath}/k3sUtil.groovy"
+
+    kvs = [:]
+    kvs.put('<service_name>', SERVICE.name)
+    kvs.put('<service_version>', SERVICE.version)
+    kvs.put('<release>', base_branch)
+    kvs.put('<COMMIT_SHA>', COMMIT_SHA)
+    kvs.put('<imagePullSecret>', K3S.pull_secretId)
+    kvs.put('<docker_image>', imageUri)
+    k3sUtils.generateDeployment(kvs, 'deployment.yaml')
+    k3sUtils.applyDeployment(K3S)
+
+    k3sUtils.generateService(kvs, 'service.yaml')
+    k3sUtils.applyService(K3S)
+}
+
+// Jenkins pipeline stages
+def execute(CONFIG, base_branch, closures=[:]) {
+
+    stage('source code check out') {
+        
+        println "loading ${sharedLibsPath}/gitUtil.groovy..."
+        def gitUtils = load "${sharedLibsPath}/gitUtil.groovy"
+
+        COMMIT_SHA = closures.GITCLONE == null ? gitUtils.clone(CONFIG.git, base_branch) : closures.GITCLONE()
+    }
+
+    stage("docker image build") {
+
+        def projDir = 'projdir'
+        if (closures.GENERATEDOCKERFILE != null)
+            projDir = closures.GENERATEDOCKERFILE()
+        else
+            projDir = generateDockerfile(CONFIG.service) //default
+
+        if (closures.BUILDIMAGE != null)
+            imageUri = closures.BUILDIMAGE()
+        else
+            imageUri = buildDockerImage(CONFIG.service, CONFIG.docker, base_branch, projDir, 'stage=STATICRES-BUILD')
+    }
+
+    stage("apply K8S Deployment") {
+        
+        if (closures.K3SDEPLOY != null) {
+            closures.K3SDEPLOY()
+            return
+        }
+
+        applyDeployment(CONFIG.service, CONFIG.k3s, base_branch)
+    }
+}
+
+def upgrade(Object CONFIG, String base_branch, String[] k8sBackupFiles, closures=[:]) {
+    stage('source code check out') {
+        
+        println "loading ${sharedLibsPath}/gitUtil.groovy..."
+        def gitUtils = load "${sharedLibsPath}/gitUtil.groovy"
+
+        COMMIT_SHA = closures.GITCLONE == null ? gitUtils.clone(CONFIG.git, base_branch) : closures.GITCLONE()
+    }
+
+    stage("docker image build") {
+
+        def projDir = 'projdir'
+        if (closures.GENERATEDOCKERFILE != null)
+            projDir = closures.GENERATEDOCKERFILE()
+        else
+            projDir = generateDockerfile(CONFIG.service) //default
+
+        if (closures.BUILDIMAGE != null)
+            imageUri = closures.BUILDIMAGE()
+        else
+            imageUri = buildDockerImage(CONFIG.service, CONFIG.docker, base_branch, projDir, 'stage=STATICRES-BUILD')
+
+        // stage("Docker Image backup") {
+            backupDockerImage(imageUri)
+            envFile = new File("${backup_destination}/${JOB_NAME}/${BUILD_NUMBER}/env")
+            envFile << "${COMMIT_SHA}\n"
+            envFile << "${base_branch}\n"
+        // }
+
+        // stage("K8s configs backup") {
+            backupK8sConfigs(k8sBackupFiles)
+        // }
+
+        cleanDockerImage(imageUri)
+    }
+
+    stage("apply K8S Deployment") {
+        
+        if (closures.K3SDEPLOY != null) {
+            closures.K3SDEPLOY()
+            return
+        }
+
+        applyDeployment(CONFIG.service, CONFIG.k3s, base_branch)
+    }
+
+    stage("Clear older backup version") {
+        // 清理旧的备份
+        dir("${backup_destination}/${JOB_NAME}") {
+            echo "-----> Clear older backup version ..."
+            sh """
+            save_file=`ls -ltr | tail -5 | awk '{print \$NF}'`
+            ls | grep -v "\$save_file" | xargs rm -rf
+            """
+        }
+    }
+}
+
+def rollback(Object CONFIG, String rollbackBuildNumber, closures=[:]) {
+
+    path = "${backup_destination}/${JOB_NAME}/${rollbackBuildNumber}"
+    if (fileExists(path)) {
+
+        envFile = new File("${backup_destination}/${JOB_NAME}/${rollbackBuildNumber}/env")
+        def lines = envFile.readLines()
+        COMMIT_SHA = lines[0]
+        BASE_BRANCH = lines[1]
+
+        stage('source code check out') {
+            print("souce code check out. Skipped!")
+        }
+
+        stage("docker image build") {
+
+            print("restore docker image from rollbackNo. #${rollbackBuildNumber}")
+            imageUri = restoreDockerImage(CONFIG.docker, path, BASE_BRANCH, rollbackBuildNumber)
+            cleanDockerImage(imageUri)
+        }
+
+        stage("apply K8S Deployment") {
+        
+            if (closures.K3SDEPLOY != null) {
+                closures.K3SDEPLOY()
+                return
+            }
+
+            dir("${backup_destination}/${JOB_NAME}/${rollbackBuildNumber}/k8s") {
+                applyDeployment(CONFIG.service, CONFIG.k3s, BASE_BRANCH)
+            }
+        }
+
+        stage("Clear rollback workspace") {
+            // 清理rollback工作目录
+            dir("${backup_destination}/${JOB_NAME}") {
+                echo "-----> Clear rollback workspace ..."
+                sh "rm -rf *@tmp"
+            }
+        }
+    }
+}
+
+return this    

+ 93 - 0
sharedLibs/stages.ssh.groovy

@@ -0,0 +1,93 @@
+sharedLibsPath = "${env.WORKSPACE}/sharedLibs"
+gitTargetPath = 'reverseSearch'
+
+def upgrade(Object CONFIG, String base_branch, closures=[:]) {
+
+    stage('source code check out') {
+        
+        println "loading ${sharedLibsPath}/gitUtil.groovy..."
+        def gitUtils = load "${sharedLibsPath}/gitUtil.groovy"
+
+        COMMIT_SHA = closures.GITCLONE == null ? gitUtils.clone(CONFIG.git, base_branch, gitTargetPath) : closures.GITCLONE()
+    }
+
+    stage('transfer files to remote') {
+
+        withCredentials([
+            sshUserPrivateKey(
+                credentialsId: CONFIG.ssh.credentialId,
+                keyFileVariable: 'SSH_KEY')
+        ]) {
+            def remote = [
+                name: 'transfer-files',
+                host: CONFIG.ssh.server,
+                user: CONFIG.ssh.username ?: "root",
+                allowAnyHosts: true,
+                identityFile: env.SSH_KEY,
+                timeoutSec: 60
+            ]
+
+            sshCommand remote: remote, command: """
+                if [ -d "${CONFIG.ssh.remotePath}/${gitTargetPath}" ]; then
+                    cd ${CONFIG.ssh.remotePath}/${gitTargetPath}
+                    my_pid=\$\$
+                    pgrep -f 'python.*restful.py' | while read pid; do
+                        [ "\$pid" = "\$my_pid" ] && continue
+                        [ "\$(readlink -f /proc/\$pid/cwd)" = "\$(pwd)" ] || continue
+                        kill "\$pid"
+                    done || true
+                    sleep 2
+                    echo '🛑 停止 Python 服务...'
+                fi
+            """
+
+            sshCommand remote: remote, command: """
+                if [ -d "${CONFIG.ssh.remotePath}/${gitTargetPath}" ]; then
+                    rm -rf "${CONFIG.ssh.remotePath}/${gitTargetPath}"
+                    echo '✅ 远程目录已清空'
+                fi
+            """
+
+            sshPut remote: remote, from: gitTargetPath, into: "${CONFIG.ssh.remotePath}"
+            echo "✅ 文件传输完成"
+        }
+    }
+
+    stage('run project') {
+
+        withCredentials([
+            sshUserPrivateKey(
+                credentialsId: CONFIG.ssh.credentialId,
+                keyFileVariable: 'SSH_KEY')
+        ]) {
+            def remote = [
+                name: 'transfer-files',
+                host: CONFIG.ssh.server,
+                user: CONFIG.ssh.username ?: "root",
+                allowAnyHosts: true,
+                identityFile: env.SSH_KEY,
+                timeoutSec: 60
+            ]
+
+            // 6.2 启动服务
+            echo '🚀 启动 Python 服务...'
+            try {
+                sshCommand remote: remote,
+                    command: """
+                        cd ${CONFIG.ssh.remotePath}/${gitTargetPath}
+                        nohup ${CONFIG.ssh.command} > runtime.log 2>&1 &
+                        echo \$! > PID
+                        sleep 3
+                        pgrep -f 'python.*restful.py' >/dev/null
+                    """
+                echo "✅ Python(${gitTargetPath}) 服务已启动 (目录: ${CONFIG.ssh.remotePath}/${gitTargetPath})"
+            } catch (Exception e) {
+                echo "❌ 命令执行失败: ${e.getMessage()}"
+                // 处理失败情况
+            }
+            // if (startStatus != 0) error "❌ 服务启动失败"
+        }
+    }
+}
+
+return this

+ 26 - 0
sharedLibs/yaml2Map.groovy

@@ -0,0 +1,26 @@
+// file -> Map
+def read(String filePath) {
+  def pipelineCfg = readYaml(file: filePath)
+  return pipelineCfg
+}
+
+// 用project 覆盖 global
+def merge(Object project, Object global) {
+  global.k3s = global.k3s + (project.k3s == null ? [:] : project.k3s)
+  global.docker = global.docker + (project.docker == null ? [:] : project.docker)
+  global.git = (global.git == null ? [:] : global.git) + (project.git == null ? [:] : project.git)
+  global.service = project.service
+  global.skywalking = project.skywalking
+
+  if (project.ssh != null) {
+    global.ssh = project.ssh
+  }
+
+  if (project.files != null) {
+    global.files = project.files
+  }
+  
+  return global
+}
+
+return this