├── settings.gradle ├── .DS_Store ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── com │ └── daluobai │ └── jenkinslib │ ├── Main.groovy │ ├── constant │ ├── GlobalShare.groovy │ ├── EBuildStatusType.groovy │ └── EFileReadType.groovy │ ├── steps │ ├── StepsDocker.groovy │ ├── StepsTomcat.groovy │ ├── StepsWeb.groovy │ ├── StepsBuildMaven.groovy │ ├── StepsBuildNpm.groovy │ ├── StepsDeploy.groovy │ ├── StepsJenkins.groovy │ ├── StepsJavaWeb.groovy │ └── StepsGit.groovy │ ├── utils │ ├── JenkinsUtils.groovy │ ├── TemplateUtils.groovy │ ├── MessageUtils.groovy │ ├── ConfigUtils.groovy │ ├── FileUtils.groovy │ ├── ConfigMergeUtils.groovy │ ├── MapUtils.groovy │ ├── AssertUtils.groovy │ ├── EndpointUtils.groovy │ ├── StrUtils.groovy │ ├── ObjUtils.groovy │ ├── IoUtils.groovy │ ├── JsonUtils.groovy │ └── HttpUtils.groovy │ └── api │ ├── KubernetesApi.groovy │ ├── FeishuApi.groovy │ ├── WecomApi.groovy │ └── DingDingApi.groovy ├── configdemo ├── uninstallJenkinsUbuntu.sh ├── uninstallJenkins.sh ├── jenkins.service ├── installJenkins.sh ├── installJenkinsUbuntu.sh ├── deployWeb.groovy ├── deploy.groovy.bak ├── deployJavaWeb_Old.groovy └── deployJavaWeb.groovy ├── resources ├── template │ ├── service │ │ └── JavaWeb.service │ └── shell │ │ └── javaWeb │ │ ├── service.sh │ │ └── serviceByPID.sh └── config │ ├── config.json │ └── settings.xml ├── .gitignore ├── README.md ├── gradlew.bat ├── vars ├── syncGit2Git.groovy ├── deployWeb.groovy └── deployJavaWeb.groovy ├── gradlew └── LICENSE /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'jenkins-shared-library' 2 | 3 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daluobai-devops/jenkins-shared-library/HEAD/.DS_Store -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daluobai-devops/jenkins-shared-library/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/Main.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib 2 | 3 | static void main(String[] args) { 4 | println "Hello world!" 5 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /configdemo/uninstallJenkinsUbuntu.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | # curl -sSL https://cdn.jsdelivr.net/gh/daluobai-devops/jenkins-shared-library@master/configdemo/uninstallJenkinsUbuntu.sh | bash 3 | systemctl stop jenkins.service 4 | apt-get remove --purge jenkins -y 5 | sudo rm -rf /var/lib/jenkins /etc/jenkins /var/log/jenkins 6 | sudo apt autoremove -y 7 | sudo apt clean -------------------------------------------------------------------------------- /configdemo/uninstallJenkins.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | # curl -sSL https://cdn.jsdelivr.net/gh/daluobai-devops/jenkins-shared-library@master/configdemo/uninstallJenkinsUbuntu.sh | bash 3 | systemctl stop jenkins.service 4 | sudo yum remove -y jenkins 5 | 6 | sudo rm -rf /var/lib/jenkins /etc/sysconfig/jenkins /var/log/jenkins 7 | sudo yum autoremove -y 8 | sudo yum clean all 9 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/constant/GlobalShare.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.constant 2 | /** 3 | * @author daluobai@outlook.com 4 | * version 1.0.0 5 | * @title 6 | * @description https://github.com/daluobai-devops/jenkins-shared-library 7 | * @create 2023/4/25 12:10 8 | */ 9 | class GlobalShare implements Serializable{ 10 | 11 | public static Map globalParameterMap = [:] 12 | 13 | } 14 | -------------------------------------------------------------------------------- /resources/template/service/JavaWeb.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=${appName} service 3 | After=syslog.target network.target 4 | 5 | [Service] 6 | Type=simple 7 | 8 | ExecStart=/bin/sh -c '${javaPath} -Dapp.name=${appName} ${runOptions} -jar ${pathRoot}/${appName}/${archiveName} ${runArgs} >/dev/null 2>&1' 9 | ExecStop=/bin/kill -15 \$MAINPID 10 | 11 | Restart=on-failure 12 | RestartSec=5 13 | 14 | User=root 15 | Group=root 16 | 17 | [Install] 18 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /configdemo/jenkins.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=jenkins service 3 | After=syslog.target network.target 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/bin/sh -c '/usr/local/jdk/jdk21/bin/java -DJENKINS_HOME=/usr/local/jenkins/home/ -Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Shanghai -jar /usr/local/jenkins/jenkins.war --httpPort=9000 >> /usr/local/jenkins/jenkins.log 2>&1' 8 | ExecStop=/bin/kill -15 $MAINPID 9 | Restart=on-failure 10 | RestartSec=5 11 | 12 | User=root 13 | Group=root 14 | 15 | [Install] 16 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Gradle template 2 | .gradle 3 | **/build/ 4 | !src/**/build/ 5 | 6 | # Ignore Gradle GUI config 7 | gradle-app.setting 8 | 9 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 10 | !gradle-wrapper.jar 11 | 12 | # Avoid ignore Gradle wrappper properties 13 | !gradle-wrapper.properties 14 | 15 | # Cache of project 16 | .gradletasknamecache 17 | 18 | # Eclipse Gradle plugin generated files 19 | # Eclipse Core 20 | .project 21 | # JDT-specific (Eclipse Java Development Tools) 22 | .classpath 23 | 24 | .idea 25 | lib 26 | 27 | -------------------------------------------------------------------------------- /resources/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "DEFAULT_CONFIG": { 3 | "docker": { 4 | "registry": { 5 | "domain": "docker.io", 6 | "credentialsId": "" 7 | } 8 | }, 9 | "git": { 10 | "credentialsId": "ssh-git" 11 | }, 12 | "agent": { 13 | "credentialsId": "ssh-jenkins" 14 | }, 15 | "repository": { 16 | "gitUrlDockerfileJavaWeb": { 17 | "url": "git@github.com:daluobai-devops/docker-library.git", 18 | "path": "package-javaweb/openjdk8" 19 | } 20 | } 21 | }, 22 | "SHARE_PARAM": { 23 | "appName": "test1" 24 | } 25 | } -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/steps/StepsDocker.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.steps 2 | 3 | import com.daluobai.jenkinslib.utils.AssertUtils/** 4 | * @author wuzhao 5 | * version 1.0.0 6 | * @title 7 | * @description 8 | * @create 2023/4/25 12:10 9 | */ 10 | class StepsDocker implements Serializable { 11 | def steps 12 | 13 | StepsDocker(steps) { this.steps = steps } 14 | 15 | def login(String registry,String userName,String password) { 16 | steps.echo("StepsDocker1:${userName} -p ${password} ${registry}") 17 | steps.sh "docker login -u ${userName} -p ${password} ${registry}" 18 | steps.echo("StepsDocker2") 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /configdemo/installJenkins.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # curl -sSL https://cdn.jsdelivr.net/gh/daluobai-devops/jenkins-shared-library@master/configdemo/installJenkins.sh | bash 3 | yum install wget -y 4 | sudo wget -O /etc/yum.repos.d/jenkins.repo \ 5 | https://pkg.jenkins.io/redhat-stable/jenkins.repo 6 | sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key 7 | sudo yum upgrade -y 8 | # Add required dependencies for the jenkins package 9 | sudo yum install fontconfig java-21-openjdk -y 10 | sudo yum install jenkins -y 11 | sudo systemctl daemon-reload 12 | systemctl enable jenkins.service 13 | systemctl start jenkins.service 14 | sed -i 's/^Environment=.*/Environment="JENKINS_PORT=9000"/' /lib/systemd/system/jenkins.service 15 | firewall-cmd --zone=public --add-port=9000/tcp --permanent && firewall-cmd --reload -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/utils/JenkinsUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.utils 2 | 3 | import com.daluobai.jenkinslib.utils.AssertUtils 4 | import com.daluobai.jenkinslib.constant.EFileReadType 5 | 6 | /** 7 | * @author daluobai@outlook.com 8 | * version 1.0.0 9 | * @title 10 | * @description https://github.com/daluobai-devops/jenkins-shared-library 11 | * @create 2023/4/25 12:10 12 | */ 13 | class JenkinsUtils implements Serializable { 14 | 15 | def steps 16 | 17 | JenkinsUtils(steps) { this.steps = steps } 18 | 19 | /** 20 | * pipeline sh 21 | * @param script 22 | * @return 23 | */ 24 | def pipelineSH(String script) { 25 | AssertUtils.notBlank(script, "script为空"); 26 | def scriptResp = steps.sh returnStdout: true, script: script 27 | def scriptRespTrim = scriptResp.trim() 28 | return scriptRespTrim 29 | } 30 | } -------------------------------------------------------------------------------- /configdemo/installJenkinsUbuntu.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | # curl -sSL https://cdn.jsdelivr.net/gh/daluobai-devops/jenkins-shared-library@master/configdemo/installJenkinsUbuntu.sh | bash 3 | sudo apt update 4 | sudo apt install fontconfig openjdk-21-jre -y 5 | sudo java -version 6 | sudo wget -O /etc/apt/keyrings/jenkins-keyring.asc \ 7 | https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key 8 | echo "deb [signed-by=/etc/apt/keyrings/jenkins-keyring.asc]" \ 9 | https://pkg.jenkins.io/debian-stable binary/ | sudo tee \ 10 | /etc/apt/sources.list.d/jenkins.list > /dev/null 11 | sudo apt update 12 | sudo DEBIAN_FRONTEND=noninteractive apt install jenkins -y 13 | 14 | #sed -i 's/^Group=.*/Group=root/' /lib/systemd/system/jenkins.service 15 | #sed -i 's/^User=.*/User=root/' /lib/systemd/system/jenkins.service 16 | sed -i 's/^Environment=.*/Environment="JENKINS_PORT=9000"/' /lib/systemd/system/jenkins.service 17 | systemctl daemon-reload 18 | systemctl start jenkins 19 | systemctl enable jenkins 20 | 21 | ufw allow 9000/tcp && ufw reload -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/constant/EBuildStatusType.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.constant 2 | 3 | /** 4 | * @author daluobai@outlook.com 5 | * version 1.0.0 6 | * @title 7 | * @description https://github.com/daluobai-devops/jenkins-shared-library 8 | * 9 | * @create 2023/4/25 12:10 10 | */ 11 | enum EBuildStatusType { 12 | 13 | FAILED("FAILED", "失败"), 14 | ABORTED("ABORTED", "终止"), 15 | SUCCESS("SUCCESS", "成功") 16 | ; 17 | private String code; 18 | 19 | private String message; 20 | 21 | EBuildStatusType(String code, String message) { 22 | this.code = code; 23 | this.message = message; 24 | } 25 | 26 | static EBuildStatusType get(String code) { 27 | EBuildStatusType[] values = values(); 28 | for (EBuildStatusType object : values) { 29 | if (object.code == code) { 30 | return object; 31 | } 32 | } 33 | return null; 34 | } 35 | 36 | @Override 37 | public String toString(){ 38 | return code 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/constant/EFileReadType.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.constant 2 | 3 | /** 4 | * @author daluobai@outlook.com 5 | * version 1.0.0 6 | * @title 7 | * @description https://github.com/daluobai-devops/jenkins-shared-library 8 | * 9 | * @create 2023/4/25 12:10 10 | */ 11 | enum EFileReadType { 12 | 13 | //URL:https://nacos.xxxx.com/nacos/v1/cs/configs?dataId=settings.xml&group=DEFAULT_GROUP 14 | URL("URL", "从URL获取"), 15 | HOST_PATH("HOST_PATH", "从宿主机目录获取"), 16 | RESOURCES("RESOURCES", "从resources目录获取") 17 | ; 18 | private String code; 19 | 20 | private String message; 21 | 22 | EFileReadType(String code, String message) { 23 | this.code = code; 24 | this.message = message; 25 | } 26 | 27 | static EFileReadType get(String code) { 28 | EFileReadType[] values = EFileReadType.values(); 29 | for (EFileReadType object : values) { 30 | if (object.code == code) { 31 | return object; 32 | } 33 | } 34 | return null; 35 | } 36 | 37 | @Override 38 | public String toString(){ 39 | return code 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/api/KubernetesApi.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.api 2 | 3 | import com.daluobai.jenkinslib.utils.AssertUtils 4 | import com.daluobai.jenkinslib.utils.JsonUtils 5 | /** 6 | * @author daluobai@outlook.com 7 | * version 1.0.0 8 | * @title 9 | * @description https://github.com/daluobai-devops/jenkins-shared-library 10 | * @create 2023/4/25 12:10 11 | */ 12 | class KubernetesApi implements Serializable { 13 | def steps 14 | 15 | KubernetesApi(steps) { this.steps = steps } 16 | 17 | /** 18 | * 获取deployment状态 19 | * @param deployment 20 | * @param namespace 21 | * @return {"availableReplicas":1,"observedGeneration":3,"readyReplicas":1,"replicas":1,"updatedReplicas":1} 22 | */ 23 | def deploymentStatus(String deployName,String namespace) { 24 | AssertUtils.notBlank(deployName,"deployName空的"); 25 | AssertUtils.notBlank(namespace,"namespace空的"); 26 | 27 | def deployStatusMap = steps.sh returnStdout: true, script: "kubectl get deploy ${deployName} -n ${namespace} -o jsonpath='{.status}'" 28 | def responseJson = null; 29 | try{ 30 | responseJson = JsonUtils.parseObj(deployStatusMap); 31 | } catch (Exception e) { 32 | e.printStackTrace(); 33 | } 34 | return responseJson; 35 | 36 | } 37 | } -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/utils/TemplateUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.utils 2 | 3 | import com.daluobai.jenkinslib.utils.AssertUtils 4 | /** 5 | * @author daluobai@outlook.com 6 | * version 1.0.0 7 | * @title 8 | * @description https://github.com/daluobai-devops/jenkins-shared-library 9 | * @create 2023/4/25 12:10 10 | */ 11 | class TemplateUtils implements Serializable { 12 | 13 | 14 | /** 15 | * GStringTemplateEngine 16 | * @param stringTemplateContent 模板 17 | * @param dataMap 数据map 18 | * @return 19 | */ 20 | @Deprecated 21 | def stringTemplate(stringTemplateContent, dataMap) { 22 | AssertUtils.notBlank(stringTemplateContent, "stringTemplateContent为空"); 23 | AssertUtils.notNull(dataMap, "dataMap为空"); 24 | 25 | def engine = new groovy.text.GStringTemplateEngine() 26 | def template = engine.createTemplate(stringTemplateContent).make(dataMap) 27 | return template.toString() 28 | } 29 | 30 | /** 31 | * GStringTemplateEngine 32 | * @param stringTemplateContent 模板 33 | * @param dataMap 数据map 34 | * @return 35 | */ 36 | static def makeTemplate(stringTemplateContent, dataMap) { 37 | AssertUtils.notBlank(stringTemplateContent, "stringTemplateContent为空"); 38 | AssertUtils.notNull(dataMap, "dataMap为空"); 39 | 40 | def engine = new groovy.text.GStringTemplateEngine() 41 | def template = engine.createTemplate(stringTemplateContent).make(dataMap) 42 | return template.toString() 43 | } 44 | } -------------------------------------------------------------------------------- /resources/template/shell/javaWeb/service.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Start the java project 4 | start() { 5 | # Check if the java process is running 6 | PID=\$(ps -ef | grep "\\-Dapp.name=${appName}\\s" | grep -v grep | awk '{print \$2}') 7 | if [ -n "\$PID" ]; then 8 | # The process is already running, do nothing 9 | echo "The app is already running, pid: \$PID" 10 | return 0 11 | fi 12 | # Start the java project in the background and redirect the output to a log file 13 | nohup ${javaPath} -Dapp.name=${appName} ${runOptions} -jar ${archiveName} ${runArgs} >/dev/null 2>&1 & 14 | # Get the pid of the java process 15 | PID=\$! 16 | # Echo the pid to the console 17 | echo "The app is started, pid: \$PID" 18 | } 19 | 20 | # Stop the java project 21 | stop() { 22 | PIDS=\$(ps -ef | grep "\\-Dapp.name=${appName}\\s" | grep -v grep | awk '{print \$2}') 23 | if [ -n "\$PIDS" ]; then 24 | for PID in \$PIDS; do 25 | # Kill the process with SIGTERM signal 26 | kill -9 \$PID 27 | # Wait for the process to exit 28 | wait \$PID 29 | # Echo a message to the console 30 | echo "The app is stopped, pid: \$PID" 31 | done 32 | return 0 33 | fi 34 | echo "The app is stopped" 35 | } 36 | 37 | # Restart the java project 38 | restart() { 39 | # Stop the java project first 40 | stop 41 | # Start the java project again 42 | start 43 | } 44 | 45 | # Check the command line argument and call the corresponding function 46 | case "\$1" in 47 | start) 48 | start 49 | ;; 50 | stop) 51 | stop 52 | ;; 53 | restart) 54 | restart 55 | ;; 56 | *) 57 | echo "Usage: \$0 {start|stop|restart}" 58 | exit 1 59 | ;; 60 | esac 61 | 62 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/api/FeishuApi.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.api 2 | 3 | import com.daluobai.jenkinslib.utils.HttpUtils 4 | import com.daluobai.jenkinslib.utils.AssertUtils 5 | import com.daluobai.jenkinslib.utils.JsonUtils 6 | import com.daluobai.jenkinslib.utils.StrUtils 7 | /** 8 | * @author daluobai@outlook.com 9 | * version 1.0.0 10 | * @title 11 | * @description https://github.com/daluobai-devops/jenkins-shared-library 12 | * @create 2023/4/25 12:10 13 | */ 14 | class FeishuApi implements Serializable { 15 | def steps 16 | 17 | FeishuApi(steps) { this.steps = steps } 18 | 19 | /** 20 | * 发送飞书消息 21 | * @param chatToken 群组ID 22 | * @param msg 消息内容 23 | * @return 24 | */ 25 | def sendMsg(String chatToken,String title,String text) { 26 | AssertUtils.notBlank(chatToken,"chatToken空的"); 27 | AssertUtils.notBlank(title,"title空的"); 28 | AssertUtils.notBlank(text,"text空的"); 29 | 30 | Map params = new HashMap<>(); 31 | params.put("title",title); 32 | params.put("text",text); 33 | 34 | String paramsStr = JsonUtils.toJsonStr(params); 35 | String response = ""; 36 | try { 37 | response = HttpUtils.post("https://open.feishu.cn/open-apis/bot/hook/"+chatToken, 38 | paramsStr); 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | } 42 | if (StrUtils.isBlank(response)){ 43 | return false 44 | } 45 | def responseJson = JsonUtils.parseObj(response); 46 | 47 | Boolean ok = responseJson.getBool("ok"); 48 | return !(ok == null || !ok); 49 | 50 | } 51 | } -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/api/WecomApi.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.api 2 | 3 | import com.daluobai.jenkinslib.utils.AssertUtils 4 | import com.daluobai.jenkinslib.utils.StrUtils 5 | import com.daluobai.jenkinslib.utils.HttpUtils 6 | import com.daluobai.jenkinslib.utils.JsonUtils 7 | 8 | /** 9 | * @author daluobai@outlook.com 10 | * version 1.0.0 11 | * @title 企业微信API 12 | * @description https://github.com/daluobai-devops/jenkins-shared-library 13 | * @create 2023/4/25 12:10 14 | */ 15 | class WecomApi implements Serializable { 16 | def steps 17 | 18 | WecomApi(steps) { this.steps = steps } 19 | 20 | /** 21 | * 发送消息 22 | * @param chatToken webhook key 23 | * @param text 消息内容 24 | * @return 25 | */ 26 | def sendMsg(String chatToken,String text) { 27 | AssertUtils.notBlank(chatToken,"chatToken空的"); 28 | AssertUtils.notBlank(text,"text空的"); 29 | 30 | def paramMap = [ 31 | "msgtype": "text", 32 | "text": [ 33 | "content": text 34 | ] 35 | ] 36 | 37 | String paramsStr = JsonUtils.toJsonStr(paramMap); 38 | String response = "" 39 | try { 40 | response = HttpUtils.HttpRequest.post("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key="+chatToken) 41 | .contentType("application/json;charset=utf-8").body(paramsStr).execute().body() 42 | } catch (Exception e) { 43 | e.printStackTrace(); 44 | } 45 | if (StrUtils.isBlank(response)){ 46 | return false 47 | } 48 | def responseJson = JsonUtils.parseObj(response) 49 | 50 | Boolean ok = responseJson.getBool("ok") 51 | return !(ok == null || !ok) 52 | 53 | } 54 | } -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/api/DingDingApi.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.api 2 | 3 | import com.daluobai.jenkinslib.utils.HttpUtils 4 | import com.daluobai.jenkinslib.utils.AssertUtils 5 | import com.daluobai.jenkinslib.utils.JsonUtils 6 | import com.daluobai.jenkinslib.utils.StrUtils 7 | /** 8 | * @author daluobai@outlook.com 9 | * version 1.0.0 10 | * @title 11 | * @description https://github.com/daluobai-devops/jenkins-shared-library 12 | * @create 2023/4/25 12:10 13 | */ 14 | class DingDingApi implements Serializable { 15 | def steps 16 | 17 | DingDingApi(steps) { this.steps = steps } 18 | 19 | /** 20 | * 发送钉钉消息 21 | * @param access_token token 22 | * @param text 消息内容 23 | * @return 24 | */ 25 | def sendMsg(String accessToken,String text) { 26 | AssertUtils.notBlank(accessToken,"accessToken空的"); 27 | AssertUtils.notBlank(text,"text空的"); 28 | Map params = new HashMap<>(); 29 | params.put("msgtype","text"); 30 | Map paramsText = new HashMap<>(); 31 | paramsText.put("content",text); 32 | params.put("text",paramsText); 33 | 34 | String paramsStr = JsonUtils.toJsonStr(params); 35 | String response = ""; 36 | try { 37 | response = HttpUtils.post("https://oapi.dingtalk.com/robot/send?access_token="+accessToken, 38 | paramsStr); 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | } 42 | if (StrUtils.isBlank(response)){ 43 | return false 44 | } 45 | def responseJson = JsonUtils.parseObj(response); 46 | 47 | def errcode = responseJson.getInt("errcode") 48 | return !(errcode == null || errcode != 0); 49 | 50 | } 51 | } -------------------------------------------------------------------------------- /resources/template/shell/javaWeb/serviceByPID.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 定义java服务的路径和名称 4 | SHELL_PATH=\$(readlink -f "\$0") 5 | SHELL_DIR=\$(dirname "\$path") 6 | SERVICE_DIR=\$SHELL_DIR 7 | SERVICE_NAME=${appName} 8 | 9 | # 定义pid文件的路径和名称 10 | PID_FILE=\$SERVICE_DIR/service.pid 11 | 12 | # 定义启动、停止和重启的函数 13 | start() { 14 | # 检查pid文件是否存在 15 | if [ -f \$PID_FILE ]; then 16 | # 读取pid文件中的进程号 17 | pid=\$(cat \$PID_FILE) 18 | # 检查进程是否存在 19 | if kill -0 \$pid >/dev/null 2>&1; then 20 | echo "\$SERVICE_NAME is already running (pid: \$pid)" 21 | return 22 | else 23 | # 如果进程不存在,删除pid文件 24 | rm -f \$PID_FILE 25 | fi 26 | fi 27 | # 启动服务,并将进程号写入pid文件 28 | echo "Starting \$SERVICE_NAME ..." 29 | nohup ${javaPath} -Dapp.name=${appName} ${runOptions} -jar ${archiveName} ${runArgs} >/dev/null 2>&1 & 30 | echo \$! > \$PID_FILE 31 | echo "\$SERVICE_NAME started" 32 | } 33 | 34 | stop() { 35 | # 检查pid文件是否存在 36 | if [ -f \$PID_FILE ]; then 37 | # 读取pid文件中的进程号 38 | pid=\$(cat \$PID_FILE) 39 | # 检查进程是否存在 40 | if kill -0 \$pid >/dev/null 2>&1; then 41 | # 停止服务,并删除pid文件 42 | echo "Stopping \$SERVICE_NAME ..." 43 | kill -9 \$pid 44 | rm -f \$PID_FILE 45 | echo "\$SERVICE_NAME stopped" 46 | else 47 | # 如果进程不存在,删除pid文件,并提示服务没有运行 48 | rm -f \$PID_FILE 49 | echo "\$SERVICE_NAME is not running" 50 | fi 51 | else 52 | # 如果pid文件不存在,提示服务没有运行 53 | echo "\$SERVICE_NAME is not running" 54 | fi 55 | } 56 | 57 | restart() { 58 | # 重启服务,相当于先停止再启动 59 | stop 60 | start 61 | } 62 | 63 | # 根据传入的参数执行相应的函数 64 | case "\$1" in 65 | start) 66 | start 67 | ;; 68 | stop) 69 | stop 70 | ;; 71 | restart) 72 | restart 73 | ;; 74 | *) 75 | # 如果没有传入参数或者参数不合法,打印帮助信息 76 | echo "Usage: \$0 {start|stop|restart}" 77 | esac 78 | 79 | exit 0 80 | 81 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/utils/MessageUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.utils 2 | 3 | import com.daluobai.jenkinslib.utils.ObjUtils 4 | import com.daluobai.jenkinslib.utils.AssertUtils 5 | import com.daluobai.jenkinslib.utils.StrUtils 6 | import com.daluobai.jenkinslib.api.FeishuApi 7 | import com.daluobai.jenkinslib.api.WecomApi 8 | import com.daluobai.jenkinslib.constant.EFileReadType 9 | import com.daluobai.jenkinslib.steps.StepsJenkins 10 | 11 | import java.nio.charset.Charset 12 | 13 | /** 14 | * @author daluobai@outlook.com 15 | * version 1.0.0 16 | * @title 17 | * @description https://github.com/daluobai-devops/jenkins-shared-library 18 | * @create 2023/4/25 12:10 19 | */ 20 | class MessageUtils implements Serializable { 21 | 22 | def steps 23 | 24 | MessageUtils(steps) { this.steps = steps } 25 | 26 | /*******************初始化全局对象 开始*****************/ 27 | def feishuApi = new FeishuApi(steps) 28 | def wecomApi = new WecomApi(steps) 29 | /*******************初始化全局对象 结束*****************/ 30 | 31 | /** 32 | * 33 | * @param fileFullPath 34 | * @param simpleMessage 这条消息是不是精简消息,精简消息每次都会发 35 | * 36 | * @return 37 | */ 38 | def sendMessage(boolean simpleMessage = true, messageConfig, String title, String content) { 39 | if (ObjUtils.isEmpty(messageConfig)) { 40 | return false 41 | } 42 | AssertUtils.notBlank(content, "content为空") 43 | //遍历messageConfig 44 | messageConfig.each { key, value -> 45 | if (key == "wecom" && StrUtils.isNotBlank(value.key)) { 46 | if (value.fullMessage || simpleMessage) { 47 | //企业微信通知 48 | wecomApi.sendMsg(value.key, content) 49 | } 50 | 51 | } 52 | if (key == "feishu" && StrUtils.isNotBlank(value.token)) { 53 | if (value.fullMessage || simpleMessage) { 54 | //飞书通知 55 | feishuApi.sendMsg(value.token, title, content) 56 | } 57 | } 58 | } 59 | return true 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/utils/ConfigUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.utils 2 | 3 | import com.daluobai.jenkinslib.utils.AssertUtils 4 | import com.daluobai.jenkinslib.utils.IoUtils 5 | import com.daluobai.jenkinslib.utils.StrUtils 6 | import com.daluobai.jenkinslib.utils.HttpUtils 7 | import com.daluobai.jenkinslib.constant.EFileReadType 8 | import java.nio.charset.Charset 9 | /** 10 | * @author daluobai@outlook.com 11 | * version 1.0.0 12 | * @title 13 | * @description https://github.com/daluobai-devops/jenkins-shared-library 14 | * @create 2023/4/25 12:10 15 | */ 16 | class ConfigUtils implements Serializable { 17 | 18 | def steps 19 | 20 | ConfigUtils(steps) { this.steps = steps } 21 | 22 | /** 23 | * 从完整路径读取配置 24 | * @param configFullPath 25 | * @return 26 | */ 27 | def readConfigFromFullPath(String configFullPath) { 28 | AssertUtils.notBlank(configFullPath, "configFullPath为空"); 29 | def configType = StrUtils.subBefore(configFullPath, ":", false) 30 | //获取后缀 31 | def path = StrUtils.subAfter(configFullPath, ":", false) 32 | EFileReadType extendConfigType = EFileReadType.get(configType) 33 | return this.readConfig(extendConfigType, path) 34 | } 35 | 36 | /** 37 | * 修改文件 38 | * @param path 文件路径 39 | * @return 40 | */ 41 | def readConfig(EFileReadType eConfigType, String path) { 42 | AssertUtils.notNull(eConfigType, "配置类型为空"); 43 | AssertUtils.notBlank(path, "path为空"); 44 | def configMap = [:] 45 | def configStr = new FileUtils(steps).readString(eConfigType, path) 46 | if (eConfigType == EFileReadType.HOST_PATH) { 47 | def file = new File(path) 48 | boolean isFile = IoUtils.isFile(file) 49 | AssertUtils.isTrue(isFile, "配置文件不存在") 50 | configMap = MapUtils.mapJsonString2Map(configStr) 51 | } else if (eConfigType == EFileReadType.RESOURCES) { 52 | configMap = MapUtils.mapJsonString2Map(configStr) 53 | } else if (eConfigType == EFileReadType.URL) { 54 | configMap = MapUtils.mapJsonString2Map(configStr) 55 | } else { 56 | throw new Exception("暂不支持的配置类型") 57 | } 58 | AssertUtils.notNull(configMap, "配置文件读取失败"); 59 | return configMap 60 | } 61 | } -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/utils/FileUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.utils 2 | 3 | import com.daluobai.jenkinslib.utils.IoUtils 4 | import com.daluobai.jenkinslib.utils.AssertUtils 5 | import com.daluobai.jenkinslib.utils.StrUtils 6 | import com.daluobai.jenkinslib.utils.HttpUtils 7 | import com.daluobai.jenkinslib.constant.EFileReadType 8 | 9 | import java.nio.charset.Charset 10 | /** 11 | * @author daluobai@outlook.com 12 | * version 1.0.0 13 | * @title 14 | * @description https://github.com/daluobai-devops/jenkins-shared-library 15 | * @create 2023/4/25 12:10 16 | */ 17 | class FileUtils implements Serializable { 18 | 19 | def steps 20 | 21 | FileUtils(steps) { this.steps = steps } 22 | 23 | /** 24 | * 从完整路径读取文件 25 | * @param fileFullPath 26 | * @return 27 | */ 28 | def readStringFromFullPath(String fileFullPath) { 29 | AssertUtils.notBlank(fileFullPath, "fileFullPath为空"); 30 | def configType = StrUtils.subBefore(fileFullPath, ":", false) 31 | //获取后缀 32 | def path = StrUtils.subAfter(fileFullPath, ":", false) 33 | EFileReadType extendConfigType = EFileReadType.get(configType) 34 | return this.readString(extendConfigType, path) 35 | } 36 | 37 | /** 38 | * 修改文件 39 | * @param path 文件路径 40 | * @return 41 | */ 42 | def readString(EFileReadType eConfigType, String path) { 43 | AssertUtils.notNull(eConfigType, "配置类型为空"); 44 | AssertUtils.notBlank(path, "path为空") 45 | def fileString = "" 46 | if (eConfigType == EFileReadType.HOST_PATH) { 47 | def file = new File(path) 48 | boolean isFile = IoUtils.isFile(file) 49 | AssertUtils.isTrue(isFile, "配置文件不存在") 50 | fileString = IoUtils.readString(file, Charset.forName("utf-8")) 51 | } else if (eConfigType == EFileReadType.RESOURCES) { 52 | fileString = steps.libraryResource path 53 | } else if (eConfigType == EFileReadType.URL) { 54 | fileString = HttpUtils.get(path) 55 | } else { 56 | throw new Exception("暂不支持的配置类型") 57 | } 58 | return fileString 59 | } 60 | 61 | /** 62 | * 通过sh写文件 63 | * @param path 64 | * @param content 65 | * @return 66 | */ 67 | def writeFileBySH(String path, String content) { 68 | //这个格式不能动,不然无法报错,content 和 EOF结束必须靠最前面 69 | steps.sh """ 70 | cat > ${path} <<-'EOF' 71 | ${content} 72 | EOF 73 | """ 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jenkins-shared-library 2 | Jenkins Pipeline Extending with Shared Libraries 3 | # 一、简介 4 | jenkins pipeline 和拓展库介绍:[https://www.jenkins.io/doc/book/pipeline/]() 5 | jenkins介绍和学习(Jenkins中文社区Rick):https://www.bilibili.com/video/BV1fp4y1r7Dd 6 | 7 | 加我微信:HELLO-WUZHAO 8 | 9 | # 二、特性 10 | 11 | - 可以通过pipeline做任何事情 12 | 13 | - 通过封装pipeline,使用一个配置文件即可发布服务。 14 | 15 | - 不依赖官方以外的 jenkins 插件。 16 | 17 | - 使用 groovy 语法,非常容易上手 18 | - 构建通过 docker,可根据不同工程选择不同的 docker 环境 19 | 20 | # 三、安装 Jenkins 21 | 22 | - Centos7+ 23 | - 安装 24 | ```shell 25 | curl -sSL https://cdn.jsdelivr.net/gh/daluobai-devops/jenkins-shared-library@master/configdemo/installJenkins.sh | bash 26 | ``` 27 | - 卸载(注意:卸载会删除jenkins home目录,所有jenkins数据都会丢失) 28 | ```shell 29 | curl -sSL https://cdn.jsdelivr.net/gh/daluobai-devops/jenkins-shared-library@master/configdemo/uninstallJenkins.sh | bash 30 | ``` 31 | - Ubuntu 32 | - 安装 33 | ```shell 34 | curl -sSL https://cdn.jsdelivr.net/gh/daluobai-devops/jenkins-shared-library@master/configdemo/installJenkinsUbuntu.sh | bash 35 | ``` 36 | - 卸载(注意:卸载会删除jenkins home目录,所有jenkins数据都会丢失) 37 | ```shell 38 | curl -sSL https://cdn.jsdelivr.net/gh/daluobai-devops/jenkins-shared-library@master/configdemo/uninstallJenkinsUbuntu.sh | bash 39 | ``` 40 | # 四、安装后操作 41 | 42 | ## 1. 访问 http://ip:9000/jenkins 43 | 44 | ## 2. 安装插件Docker、Docker Pipeline、Docker Api、Pipeline Utility Steps、 45 | ## 3. 配置Jenkins Pipeline 46 | 47 | ### a.密钥管理 48 | 49 | 系统管理>凭据>系统>全局凭据>新建凭据 50 | 51 | 新建ssh-jenkins(用来免密登录服务器)和ssh-git(用来 clone 代码) 两个凭据,类型为SSH Username with private key,填入用户名和私钥 52 | 53 | 新建docker-secret(用来登录 docker 镜像)凭据类型为Username with password,如果用不到,账号密码就随便填一个 54 | 55 | ### b.共享库配置 56 | 57 | 系统管理>系统配置> Global Pipeline Libraries(Global Trusted Pipeline Libraries) 58 | 59 | name:jenkins-shared-library 60 | 61 | Default version:main 62 | 63 | 项目仓库:git@gitee.com:daluobai-devops/jenkins-shared-library.git 64 | 65 | 凭据:无(如果是你公司自己的仓库,这里选ssh-git) 66 | 67 | ### c.节点配置 68 | 69 | 配置示例: 70 | 71 | - 名称:节点的名称,唯一值,可以用来设置到发布节点labels参数 72 | 73 | - Number of executors:一般设置为 cpu 数量,不过我一般设置10 74 | - 远程工作目录:/path/jenkins/ 75 | - 标签:用来标识节点,多个节点可用同一个标签,比如给节点添加上 docker标签表示这个节点上安装了 docker 76 | - 启动方式:Launch agents via SSH 77 | - 主机:服务器的 ip 78 | - Credentials:ssh-jenkins 79 | - Host Key Verification Strategy:Manually trusted key Verification Strategy 80 | - 高级 81 | - Java 路径:/usr/local/jdk/jdk17/bin/java 82 | 83 | 构建节点配置: 84 | 85 | - 在主节点添加master标签 86 | - 在用来构建的节点添加buildNode标签,如果是jenkins安装节点作为构建节点,就要添加master和buildNode两个标签 87 | 88 | 用来测试发布服务的节点: 89 | 90 | - 名称:NODE-DEMO 91 | 92 | ## 4. 宿主机配置 93 | 94 | 构建节点:安装 docker 95 | 96 | # 五、发布第一个服务(可选) 97 | 98 | 新建job>流水线(pipeline)>配置 99 | 勾选: 100 | 101 | 不允许并发构建&&丢弃先前构建 102 | 103 | 使用 Groovy 沙盒 104 | 105 | 流水线>定义>Pipeline script填入configdemo/deployJavaWeb.groovy 106 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/steps/StepsTomcat.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.steps 2 | 3 | @Grab('org.reflections:reflections:0.9.9-RC1') 4 | import com.daluobai.jenkinslib.utils.DateUtils 5 | import com.daluobai.jenkinslib.utils.AssertUtils 6 | import com.daluobai.jenkinslib.utils.ObjUtils 7 | import com.daluobai.jenkinslib.utils.StrUtils 8 | import com.daluobai.jenkinslib.constant.GlobalShare 9 | import com.daluobai.jenkinslib.utils.EndpointUtils 10 | import com.daluobai.jenkinslib.utils.TemplateUtils 11 | 12 | /** 13 | * @author daluobai@outlook.com 14 | * version 1.0.0 15 | * @title 发布到tomcat 16 | * @description https://github.com/daluobai-devops/jenkins-shared-library 17 | * @create 2023/4/25 12:10 18 | */ 19 | class StepsTomcat implements Serializable { 20 | def steps 21 | 22 | StepsTomcat(steps) { this.steps = steps } 23 | 24 | /*******************初始化全局对象 开始*****************/ 25 | def stepsJenkins = new StepsJenkins(steps) 26 | def endpointUtils = new EndpointUtils(steps) 27 | /*******************初始化全局对象 结束*****************/ 28 | 29 | //发布 30 | def deploy(Map parameterMap) { 31 | steps.echo "StepsJavaWeb:${parameterMap}" 32 | AssertUtils.notEmpty(parameterMap,"参数为空") 33 | def enable = parameterMap.enable 34 | if (enable == false) { 35 | steps.echo "StepsTomcat.deploy不执行" 36 | return 37 | } 38 | def tomcatHome = parameterMap.tomcatHome 39 | def deployPath = parameterMap.deployPath 40 | def command = parameterMap.command 41 | def globalParameterMap = steps.globalParameterMap 42 | def appName = globalParameterMap.SHARE_PARAM.appName 43 | def archiveName = globalParameterMap.SHARE_PARAM.archiveName 44 | //获取文件名后缀 45 | def archiveSuffix = StrUtils.subAfter(archiveName, ".", true) 46 | //获取文件名 47 | def archiveOnlyName = StrUtils.subBefore(archiveName, ".", true) 48 | 49 | def backAppName = "app-" + DateUtils.format(new Date(), "yyyyMMddHHmmss") + "." + archiveSuffix 50 | // steps.withCredentials([steps.sshUserPrivateKey(credentialsId: 'ssh-jenkins', keyFileVariable: 'SSH_KEY_PATH')]) { 51 | // steps.sh "mkdir -p ~/.ssh && chmod 700 ~/.ssh && rm -f ~/.ssh/id_rsa && cp \${SSH_KEY_PATH} ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa" 52 | // } 53 | 54 | steps.unstash("appPackage") 55 | steps.sh "hostname" 56 | steps.sh "ls -l package" 57 | steps.sh "mkdir -p ${deployPath}/ && mkdir -p ${tomcatHome} && mkdir -p ${tomcatHome}/backup/${appName}" 58 | //备份 59 | steps.sh "mv ${deployPath}/${archiveName} ${tomcatHome}/backup/${appName}/${backAppName}/ || true" 60 | steps.dir("${tomcatHome}/backup/${appName}/"){ 61 | steps.sh "find . -mtime +3 -delete" 62 | } 63 | //拷贝新的包到发布目录 64 | steps.sh "cp package/${archiveName} ${deployPath}" 65 | //创建一个 app 文件夹把包解压到里面 66 | steps.sh "rm -rf ${deployPath}/${archiveOnlyName}/ || true" 67 | //切换到发布目录 68 | steps.dir("${deployPath}/"){ 69 | if (ObjUtils.isNotEmpty(command)){ 70 | steps.sh "${command}" 71 | } 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /configdemo/deployWeb.groovy: -------------------------------------------------------------------------------- 1 | def customConfig = [ 2 | //公共参数 3 | "SHARE_PARAM" : [ 4 | //app 名称,如果没填则使用jenkins job名称。可不填 5 | "appName": "testWeb", 6 | //消息通知,可不填 7 | "message": [ 8 | //企业微信通知 可不填 9 | "wecom" : [ 10 | //企业微信机器人token 必填 11 | "key": "", 12 | //是否发送完整的消息,如果为true只发送部署成功的消息。默认false 13 | "fullMessage": false 14 | ], 15 | //飞书通知 可不填 16 | "feishu": [ 17 | //飞书机器人token 必填 18 | "token" : "", 19 | //是否发送完整的消息,如果为true只发送部署成功的消息。默认false 20 | "fullMessage": false 21 | ] 22 | ] 23 | ], 24 | //发布流程 25 | "DEPLOY_PIPELINE": [ 26 | //构建 27 | "stepsBuildNpm": [ 28 | //是否激活,默认true 29 | "enable": true, 30 | //app git url 必填. 31 | "gitUrl": "https://gitee.com/log4j/pig-ui.git", 32 | //git 分支 33 | "gitBranch": "master", 34 | //构建命令 必填 35 | "buildCMD" : "npm install && npm run build", 36 | //用来打包的镜像 可不填。默认10.16.0,选项(10.16.0,14.21.3,16) 37 | "dockerBuildImage" : "registry.cn-hangzhou.aliyuncs.com/wuzhaozhongguo/build-npm:10.16.0", 38 | //使用缓存node_modules 可不填,默认true 39 | "cacheNodeModules": true 40 | ], 41 | //存储 42 | "stepsStorage" : [ 43 | //是否激活,默认true 44 | "enable": true, 45 | //构建产物类型 JAR,WAR,ZIP,TAR 必填 46 | "archiveType":"TAR", 47 | //存储构建产物,构建成功后可以在页面下载构建产物,默认false。 48 | "archiveArtifacts" : false, 49 | //存储类型 jenkinsStash,dockerRegistry 必填 50 | "jenkinsStash" : [ 51 | //是否激活,默认false 52 | "enable": true, 53 | ], 54 | //存档 55 | "archiveArtifacts" : false, 56 | "dockerRegistry" : [ 57 | //是否激活,默认false 58 | "enable": false, 59 | ], 60 | 61 | ], 62 | //发布 63 | "stepsJavaWebDeployToWebServer" : [ 64 | //是否激活,默认true 65 | "enable": true, 66 | //服务发布根目录 必填 67 | "pathRoot" : "/apps/application/projectGroup/web/", 68 | //服务发布服务label 必填 69 | "labels" : ["NODE-DEMO"] 70 | ] 71 | ], 72 | //默认配置 73 | "DEFAULT_CONFIG": [ 74 | "docker": [ 75 | "registry": [ 76 | "domain": "docker.io" 77 | ] 78 | ] 79 | ], 80 | // //继承配置 81 | "CONFIG_EXTEND" : [ 82 | //配置文件完整路径configType:path,支持URL,HOST_PATH,RESOURCES,默认RESOURCES. 必填. 83 | "configFullPath": "RESOURCES:config/extendConfigWeb.json", 84 | ] 85 | ] 86 | @Library('jenkins-shared-library') _ 87 | deployWeb(customConfig) -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/steps/StepsWeb.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.steps 2 | 3 | import com.daluobai.jenkinslib.utils.DateUtils 4 | import com.daluobai.jenkinslib.utils.AssertUtils 5 | import com.daluobai.jenkinslib.utils.ObjUtils 6 | import com.daluobai.jenkinslib.utils.StrUtils 7 | import com.daluobai.jenkinslib.constant.GlobalShare 8 | import com.daluobai.jenkinslib.utils.TemplateUtils 9 | /** 10 | * @author daluobai@outlook.com 11 | * version 1.0.0 12 | * @title 13 | * @description https://github.com/daluobai-devops/jenkins-shared-library 14 | * @create 2023/4/25 12:10 15 | */ 16 | class StepsWeb implements Serializable { 17 | def steps 18 | 19 | StepsWeb(steps) { this.steps = steps } 20 | 21 | /*******************初始化全局对象 开始*****************/ 22 | def stepsJenkins = new StepsJenkins(steps) 23 | /*******************初始化全局对象 结束*****************/ 24 | 25 | //发布 26 | def deploy(Map parameterMap) { 27 | steps.echo "StepsJavaWeb:${parameterMap}" 28 | AssertUtils.notEmpty(parameterMap,"参数为空") 29 | def labels = parameterMap.labels 30 | def pathRoot = parameterMap.pathRoot 31 | def globalParameterMap = steps.globalParameterMap 32 | def appName = globalParameterMap.SHARE_PARAM.appName 33 | def archiveName = globalParameterMap.SHARE_PARAM.archiveName 34 | def configStepsStorage = globalParameterMap.DEPLOY_PIPELINE.stepsStorage 35 | //获取文件名后缀 36 | def archiveSuffix = StrUtils.subAfter(archiveName, ".", true) 37 | AssertUtils.notEmpty(labels,"labels为空") 38 | 39 | def backAppName = "app-" + DateUtils.format(new Date(), "yyyyMMddHHmmss") + "." + archiveSuffix 40 | // steps.withCredentials([steps.sshUserPrivateKey(credentialsId: 'ssh-jenkins', keyFileVariable: 'SSH_KEY_PATH')]) { 41 | // steps.sh "mkdir -p ~/.ssh && chmod 700 ~/.ssh && rm -f ~/.ssh/id_rsa && cp \${SSH_KEY_PATH} ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa" 42 | // } 43 | labels.each{ c -> 44 | def label = c 45 | steps.echo "发布第一个标签:${label}" 46 | def nodeDeployNodeList = stepsJenkins.getNodeByLabel(label) 47 | steps.echo "获取到发布节点:${nodeDeployNodeList}" 48 | if (ObjUtils.isEmpty(nodeDeployNodeList)) { 49 | steps.error '没有可用的发布节点' 50 | } 51 | nodeDeployNodeList.each{ d -> 52 | def nodeDeployNode = d 53 | steps.echo "开始发布:${nodeDeployNode}" 54 | steps.node(nodeDeployNode) { 55 | steps.unstash("appPackage") 56 | steps.sh "hostname" 57 | steps.sh "ls -l package" 58 | steps.sh "mkdir -p ${pathRoot}/${appName} && mkdir -p ${pathRoot}/${appName}/backup" 59 | //备份 60 | steps.sh "mv -f ${pathRoot}/${appName}/${archiveName} ${pathRoot}/${appName}/backup/${backAppName} || true" 61 | steps.dir("${pathRoot}/${appName}/backup/"){ 62 | steps.sh "find . -mtime +3 -delete" 63 | } 64 | //拷贝新的包到发布目录 65 | steps.sh "cp package/${archiveName} ${pathRoot}/${appName}" 66 | //创建一个 app 文件夹把包解压到里面 67 | steps.sh "rm -rf ${pathRoot}/${appName}/app || true" 68 | steps.sh "mkdir -p ${pathRoot}/${appName}/app" 69 | //切换到发布目录 70 | steps.dir("${pathRoot}/${appName}/"){ 71 | if (configStepsStorage.archiveType == "ZIP"){ 72 | steps.sh "unzip ${archiveName} -d app/" 73 | }else { 74 | steps.sh "tar -zxvf ${archiveName} -C app/" 75 | } 76 | steps.sh "ls -l app" 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/utils/ConfigMergeUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.utils 2 | 3 | /** 4 | * @author daluobai@outlook.com 5 | * version 1.0.0 6 | * @title 配置合并工具类 - 替代 com.typesafe.config 7 | * @description https://github.com/daluobai-devops/jenkins-shared-library 8 | * @create 2025/12/13 9 | */ 10 | class ConfigMergeUtils implements Serializable { 11 | 12 | /** 13 | * 将Jenkins参数动态合并到配置Map中 14 | * 支持嵌套路径,如 "SHARE_PARAM.appName" 15 | * @param configMap 配置Map 16 | * @param params Jenkins参数Map 17 | * @return 合并后的新Map 18 | */ 19 | static Map mergeParams(Map configMap, Map params) { 20 | if (ObjUtils.isEmpty(params)) { 21 | return MapUtils.deepCopy(configMap) 22 | } 23 | 24 | // 深拷贝配置,避免修改原始配置 25 | Map result = MapUtils.deepCopy(configMap) 26 | 27 | // 遍历所有参数 28 | params.each { key, value -> 29 | // 支持嵌套路径,如 "SHARE_PARAM.appName" 30 | setNestedValue(result, key, value) 31 | } 32 | 33 | return result 34 | } 35 | 36 | /** 37 | * 设置嵌套路径的值 38 | * @param map 目标Map 39 | * @param path 路径,支持点分隔,如 "SHARE_PARAM.appName" 40 | * @param value 值 41 | */ 42 | private static void setNestedValue(Map map, String path, Object value) { 43 | if (StrUtils.isBlank(path)) { 44 | return 45 | } 46 | 47 | // 如果路径不包含点,直接设置 48 | if (!path.contains('.')) { 49 | map.put(path, value) 50 | return 51 | } 52 | 53 | // 分割路径 54 | def parts = path.split('\\.') 55 | def current = map 56 | 57 | // 遍历路径,创建嵌套Map 58 | for (int i = 0; i < parts.length - 1; i++) { 59 | def part = parts[i] 60 | 61 | // 如果当前键不存在或不是Map,创建新Map 62 | if (!current.containsKey(part) || !(current[part] instanceof Map)) { 63 | current[part] = [:] 64 | } 65 | 66 | current = current[part] 67 | } 68 | 69 | // 设置最后一个键的值 70 | current.put(parts[parts.length - 1], value) 71 | } 72 | 73 | /** 74 | * 获取嵌套路径的值 75 | * @param map 源Map 76 | * @param path 路径,支持点分隔 77 | * @param defaultValue 默认值 78 | * @return 值 79 | */ 80 | static Object getNestedValue(Map map, String path, Object defaultValue = null) { 81 | if (ObjUtils.isEmpty(map) || StrUtils.isBlank(path)) { 82 | return defaultValue 83 | } 84 | 85 | // 如果路径不包含点,直接获取 86 | if (!path.contains('.')) { 87 | return map.getOrDefault(path, defaultValue) 88 | } 89 | 90 | // 分割路径 91 | def parts = path.split('\\.') 92 | def current = map 93 | 94 | // 遍历路径 95 | for (def part : parts) { 96 | if (!(current instanceof Map) || !current.containsKey(part)) { 97 | return defaultValue 98 | } 99 | current = current[part] 100 | } 101 | 102 | return current ?: defaultValue 103 | } 104 | 105 | /** 106 | * 检查嵌套路径是否存在 107 | * @param map 源Map 108 | * @param path 路径 109 | * @return 是否存在 110 | */ 111 | static boolean hasPath(Map map, String path) { 112 | if (ObjUtils.isEmpty(map) || StrUtils.isBlank(path)) { 113 | return false 114 | } 115 | 116 | // 如果路径不包含点,直接检查 117 | if (!path.contains('.')) { 118 | return map.containsKey(path) 119 | } 120 | 121 | // 分割路径 122 | def parts = path.split('\\.') 123 | def current = map 124 | 125 | // 遍历路径 126 | for (def part : parts) { 127 | if (!(current instanceof Map) || !current.containsKey(part)) { 128 | return false 129 | } 130 | current = current[part] 131 | } 132 | 133 | return true 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/utils/MapUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.utils 2 | 3 | import groovy.json.JsonSlurper 4 | import groovy.json.JsonSlurperClassic 5 | /** 6 | * @author daluobai@outlook.com 7 | * version 1.0.0 8 | * @title 9 | * @description 参考https://github.com/SAP/jenkins-library 10 | * @create 2023/4/25 12:10 11 | */ 12 | class MapUtils implements Serializable { 13 | static boolean isMap(object){ 14 | return object in Map 15 | } 16 | 17 | static Map pruneNulls(Map m) { 18 | 19 | Map result = [:] 20 | 21 | m = m ?: [:] 22 | 23 | m.each { key, value -> 24 | if(isMap(value)) 25 | result[key] = pruneNulls(value) 26 | else if(value != null) 27 | result[key] = value 28 | } 29 | return result 30 | } 31 | 32 | /** 33 | * Merge two maps with the second one has precedence 34 | * @param base First map 35 | * @param overlay Second map, takes precedence 36 | * @return The merged map 37 | */ 38 | static Map merge(Map base, Map overlay) { 39 | 40 | Map result = [:] 41 | 42 | base = base ?: [:] 43 | 44 | result.putAll(base) 45 | 46 | overlay.each { key, value -> 47 | result[key] = isMap(value) ? merge(base[key], value) : value 48 | } 49 | return result 50 | } 51 | 52 | /** 53 | * 合并map,从右向左 54 | * @param maps 55 | * @return 56 | */ 57 | static def merge(List maps) { 58 | def injectMap = [:] 59 | for (i in 0..strategy 70 | * should be applied. 71 | * The strategy is also applied to all sub-maps contained as values 72 | * in m in a recursive manner. 73 | * @param strategy Strategy applied to all non-map entries 74 | */ 75 | static void traverse(Map m, Closure strategy) { 76 | 77 | def updates = [:] 78 | m.each { key, value -> 79 | if(isMap(value)) { 80 | traverse(value, strategy) 81 | } else { 82 | // do not update the map while it is traversed. Depending 83 | // on the map implementation the behavior is undefined. 84 | updates.put(key, strategy(value)) 85 | } 86 | } 87 | m.putAll(updates) 88 | } 89 | 90 | static private def getByPath(Map m, def key) { 91 | List path = key in CharSequence ? key.tokenize('/') : key 92 | 93 | def value = m.get(path.head()) 94 | 95 | if (path.size() == 1) return value 96 | if (value in Map) return getByPath(value, path.tail()) 97 | 98 | return null 99 | } 100 | 101 | /* 102 | * Provides a new map with the same content like the original map. 103 | * Nested Collections and Maps are copied. Values with are not 104 | * Collections/Maps are not copied/cloned. 105 | * <paranoia>&/ltThe keys are also not copied/cloned, even if they are 106 | * Maps or Collections;paranoia> 107 | */ 108 | static deepCopy(Map original) { 109 | Map copy = [:] 110 | for (def e : original.entrySet()) { 111 | if(e.value == null) { 112 | copy.put(e.key, e.value) 113 | } else { 114 | copy.put(e.key, deepCopy(e.value)) 115 | } 116 | } 117 | copy 118 | } 119 | 120 | /* private */ static deepCopy(Set original) { 121 | Set copy = [] 122 | for(def e : original) 123 | copy << deepCopy(e) 124 | copy 125 | } 126 | 127 | /* private */ static deepCopy(List original) { 128 | List copy = [] 129 | for(def e : original) 130 | copy << deepCopy(e) 131 | copy 132 | } 133 | 134 | /* 135 | * In fact not a copy, but a catch all for everything not matching 136 | * with the other signatures 137 | */ 138 | /* private */ static deepCopy(def original) { 139 | original 140 | } 141 | 142 | /** 143 | * groovy map Json字符串转 map 144 | * @param mapString {key1:value1, key2:value2} 145 | */ 146 | static def mapJsonString2Map(String mapJsonString) { 147 | def map = new JsonSlurperClassic().parseText(mapJsonString) 148 | return map 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/steps/StepsBuildMaven.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.steps 2 | 3 | import com.daluobai.jenkinslib.utils.StrUtils 4 | import com.daluobai.jenkinslib.utils.AssertUtils 5 | import com.daluobai.jenkinslib.utils.ConfigUtils 6 | import com.daluobai.jenkinslib.utils.FileUtils 7 | /** 8 | * @author daluobai@outlook.com 9 | * version 1.0.0 10 | * @title 11 | * @description https://github.com/daluobai-devops/jenkins-shared-library 12 | * @create 2023/4/25 12:10 13 | */ 14 | class StepsBuildMaven implements Serializable { 15 | def steps 16 | 17 | StepsBuildMaven(steps) { this.steps = steps } 18 | 19 | /*******************初始化全局对象 开始*****************/ 20 | def stepsGit = new StepsGit(steps) 21 | def configUtils = new ConfigUtils(steps) 22 | def fileUtils = new FileUtils(steps) 23 | /*******************初始化全局对象 结束*****************/ 24 | 25 | //构建 26 | def build(Map configMap) { 27 | //默认配置 28 | def configDefault = configMap["DEFAULT_CONFIG"]//默认配置 29 | //共享配置 30 | def configShare = configMap["SHARE_PARAM"] 31 | //流程配置 32 | def configSteps = configMap.DEPLOY_PIPELINE.stepsBuild.stepsBuildMaven 33 | 34 | AssertUtils.notNull(configDefault, "DEFAULT_CONFIG为空") 35 | AssertUtils.notNull(configShare, "SHARE_PARAM为空") 36 | AssertUtils.notNull(configSteps, "DEPLOY_PIPELINE.stepsBuildMaven为空") 37 | 38 | def pathBase = "${steps.env.WORKSPACE}" 39 | //docker-构建产物目录 40 | def pathPackage = "package" 41 | //docker-代码目录 42 | def pathCode = "code" 43 | //存放临时sshkey的目录 44 | def pathSSHKey = "sshkey" 45 | 46 | steps.sh "mkdir -p ${steps.env.WORKSPACE}/${pathPackage}" 47 | steps.sh "mkdir -p ${steps.env.WORKSPACE}/${pathCode}" 48 | steps.sh "mkdir -p ${steps.env.WORKSPACE}/${pathSSHKey}" 49 | 50 | def dockerBuildImage = StrUtils.isNotBlank(configSteps.dockerBuildImage) ? configSteps.dockerBuildImage : "registry.cn-hangzhou.aliyuncs.com/wuzhaozhongguo/build-maven:3-jdk8" 51 | def dockerBuildImageUrl = "${dockerBuildImage}" 52 | 53 | //获取settings.xml配置,如果没有设置则为空 54 | def settingsXmlStr = StrUtils.isNotBlank(configSteps.settingsFullPath) ? fileUtils.readStringFromFullPath(configSteps.settingsFullPath) : null 55 | 56 | //如果没有提供登录密钥则不登录 57 | def dockerLoginDomain = StrUtils.isNotBlank(configDefault.docker.registry.credentialsId) ? "https://${configDefault.docker.registry.domain}" : "" 58 | def dockerLoginCredentialsId = StrUtils.isNotBlank(configDefault.docker.registry.credentialsId) ? configDefault.docker.registry.credentialsId : "" 59 | 60 | steps.withDockerRegistry(credentialsId: dockerLoginCredentialsId, url: dockerLoginDomain) { 61 | steps.sh "whoami" 62 | def mavenImage = steps.docker.image("${dockerBuildImageUrl}") 63 | mavenImage.pull() 64 | 65 | def mvnCMDSubMod = "-pl ${configSteps.subModule} -am -amd" 66 | def mvnCMDActiveProfile = StrUtils.isNotEmpty(configSteps.activeProfile) ? "-P ${configSteps.activeProfile}" : "" 67 | 68 | //这里默认会把工作空间挂载到容器中的${steps.env.WORKSPACE}目录 69 | mavenImage.inside("--entrypoint '' -v maven-repo:/root/.m2/repository") { 70 | //从 jenkins 凭据管理中获取密钥文件路径并且拷贝到工作目录下的ssh-git目录,后面clone的时候指定密钥为这个 71 | stepsGit.saveJenkinsSSHKey('ssh-git',"${steps.env.WORKSPACE}/${pathSSHKey}/ssh-git") 72 | //生成known_hosts 73 | stepsGit.sshKeyscan("${configSteps.gitUrl}", "~/.ssh/known_hosts") 74 | //如果有settings.xml配置则写入用户自定义配置. 75 | if (StrUtils.isNotBlank(settingsXmlStr)){ 76 | fileUtils.writeFileBySH("~/.m2/settings.xml", settingsXmlStr) 77 | } 78 | steps.sh """ 79 | #! /bin/sh -e 80 | mkdir -p ${pathBase}/${pathPackage} && mkdir -p ${pathBase}/${pathCode} 81 | cd ${pathBase}/${pathCode} 82 | git config --global http.version HTTP/1.1 83 | GIT_SSH_COMMAND='ssh -i ${steps.env.WORKSPACE}/${pathSSHKey}/ssh-git/id_rsa' git clone ${configSteps.gitUrl} --branch ${configSteps.gitBranch} --single-branch --depth 1 --quiet 84 | mv ${pathBase}/${pathCode}/\$(ls -A1 ${pathBase}/${pathCode}/) ${pathBase}/${pathCode}/${pathCode} 85 | cd ${pathBase}/${pathCode}/${pathCode} 86 | git log --pretty=format:"%h -%an,%ar : %s" -1 87 | git config core.ignorecase false 88 | mvn -Dmaven.test.skip=${configSteps.skipTest} ${configSteps.lifecycle} -Dmaven.compile.fork=true -U -B ${mvnCMDSubMod} ${mvnCMDActiveProfile} 89 | ls -al ${pathBase}/${pathCode}/${pathCode}/${configSteps.subModule}/target 90 | cp -r ${pathBase}/${pathCode}/${pathCode}/${configSteps.subModule}/target/* ${pathBase}/${pathPackage}/ 91 | """ 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/steps/StepsBuildNpm.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.steps 2 | 3 | import com.daluobai.jenkinslib.utils.AssertUtils 4 | import com.daluobai.jenkinslib.utils.ObjUtils 5 | import com.daluobai.jenkinslib.utils.StrUtils 6 | import com.daluobai.jenkinslib.utils.ConfigUtils 7 | import com.daluobai.jenkinslib.utils.FileUtils 8 | /** 9 | * @author daluobai@outlook.com 10 | * version 1.0.0 11 | * @title 12 | * @description https://github.com/daluobai-devops/jenkins-shared-library 13 | * @create 2023/4/25 12:10 14 | */ 15 | class StepsBuildNpm implements Serializable { 16 | def steps 17 | 18 | StepsBuildNpm(steps) { this.steps = steps } 19 | 20 | /*******************初始化全局对象 开始*****************/ 21 | def stepsGit = new StepsGit(steps) 22 | def configUtils = new ConfigUtils(steps) 23 | def fileUtils = new FileUtils(steps) 24 | /*******************初始化全局对象 结束*****************/ 25 | 26 | //构建 27 | def build(Map configMap) { 28 | //默认配置 29 | def configDefault = configMap["DEFAULT_CONFIG"]//默认配置 30 | //共享配置 31 | def configShare = configMap["SHARE_PARAM"] 32 | //流程配置-构建 33 | def configSteps = configMap.DEPLOY_PIPELINE.stepsBuildNpm 34 | //流程配置-存储 35 | def configStepsStorage = configMap.DEPLOY_PIPELINE.stepsStorage 36 | 37 | AssertUtils.notNull(configDefault, "DEFAULT_CONFIG为空") 38 | AssertUtils.notNull(configShare, "SHARE_PARAM为空") 39 | AssertUtils.notNull(configSteps, "DEPLOY_PIPELINE.stepsBuildNpm为空") 40 | 41 | def pathBase = "${steps.env.WORKSPACE}" 42 | //docker-构建产物目录 43 | def pathPackage = "package" 44 | //docker-代码目录 45 | def pathCode = "code" 46 | //存放临时sshkey的目录 47 | def pathSSHKey = "sshkey" 48 | 49 | steps.sh "mkdir -p ${steps.env.WORKSPACE}/${pathPackage}" 50 | steps.sh "mkdir -p ${steps.env.WORKSPACE}/${pathCode}" 51 | steps.sh "mkdir -p ${steps.env.WORKSPACE}/${pathSSHKey}" 52 | 53 | def dockerBuildImage = StrUtils.isNotBlank(configSteps.dockerBuildImage) ? configSteps.dockerBuildImage : "registry.cn-hangzhou.aliyuncs.com/wuzhaozhongguo/build-npm:10.16.0" 54 | def dockerBuildImageUrl = "${dockerBuildImage}" 55 | 56 | //如果没有提供登录密钥则不登录 57 | def dockerLoginDomain = StrUtils.isNotBlank(configDefault.docker.registry.credentialsId) ? "https://${configDefault.docker.registry.domain}" : "" 58 | def dockerLoginCredentialsId = StrUtils.isNotBlank(configDefault.docker.registry.credentialsId) ? configDefault.docker.registry.credentialsId : "" 59 | 60 | steps.withDockerRegistry(credentialsId: dockerLoginCredentialsId, url: dockerLoginDomain) { 61 | 62 | def mavenImage = steps.docker.image("${dockerBuildImageUrl}") 63 | mavenImage.pull() 64 | 65 | def mvnCMDSubMod = "-pl ${configSteps.subModule} -am -amd" 66 | def mvnCMDActiveProfile = "-P ${configSteps.activeProfile}" 67 | 68 | //容器中缓存modules文件夹的根路径 69 | def dockerModulesPath = "/root/.npm/" 70 | //容器中缓存modules文件夹的项目路径 71 | def dockerModulesProjectPath = "${dockerModulesPath}/${steps.currentBuild.projectName}" 72 | //这里默认会把工作空间挂载到容器中的${steps.env.WORKSPACE}目录 73 | mavenImage.inside("--entrypoint '' -v npm-repo:${dockerModulesPath}") { 74 | //从 jenkins 凭据管理中获取密钥文件路径并且拷贝到~/.ssh/id_rsa 75 | stepsGit.saveJenkinsSSHKey('ssh-git',"${steps.env.WORKSPACE}/${pathSSHKey}/ssh-git") 76 | //生成known_hosts 77 | stepsGit.sshKeyscan("${configSteps.gitUrl}", "~/.ssh/known_hosts") 78 | //不使用缓存node_modules 79 | if (ObjUtils.isNotEmpty(configSteps["cacheNodeModules"]) && !configSteps["cacheNodeModules"]){ 80 | // steps.sh "rm -rf ${dockerModulesProjectPath}/node_modules || true" 81 | } 82 | steps.sh """ 83 | #! /bin/sh -e 84 | mkdir -p ${pathBase}/${pathPackage} && mkdir -p ${pathBase}/${pathCode} && mkdir -p ${dockerModulesProjectPath} 85 | cd ${pathBase}/${pathCode} 86 | git config --global http.version HTTP/1.1 87 | GIT_SSH_COMMAND='ssh -i ${steps.env.WORKSPACE}/${pathSSHKey}/ssh-git/id_rsa' git clone ${configSteps.gitUrl} --branch ${configSteps.gitBranch} --single-branch --depth 1 --quiet 88 | mv ${pathBase}/${pathCode}/\$(ls -A1 ${pathBase}/${pathCode}/) ${pathBase}/${pathCode}/${pathCode} 89 | cd ${pathBase}/${pathCode}/${pathCode} 90 | git log --pretty=format:"%h -%an,%ar : %s" -1 91 | git config core.ignorecase false 92 | ${configSteps.buildCMD} 93 | ls -al ${pathBase}/${pathCode}/${pathCode}/dist 94 | cd ${pathBase}/${pathCode}/${pathCode}/ 95 | ${configStepsStorage.archiveType == "ZIP" ? "zip -r ${pathBase}/${pathPackage}/app.zip ./dist" : "tar -czvf ${pathBase}/${pathPackage}/app.tar.gz -C ${pathBase}/${pathCode}/${pathCode}/dist ."} 96 | """ 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /vars/syncGit2Git.groovy: -------------------------------------------------------------------------------- 1 | import com.daluobai.jenkinslib.utils.AssertUtils 2 | import com.daluobai.jenkinslib.utils.StrUtils 3 | import com.daluobai.jenkinslib.utils.ObjUtils 4 | import com.daluobai.jenkinslib.constant.EBuildStatusType 5 | import com.daluobai.jenkinslib.constant.EFileReadType 6 | import com.daluobai.jenkinslib.steps.StepsBuildNpm 7 | import com.daluobai.jenkinslib.steps.StepsGit 8 | import com.daluobai.jenkinslib.steps.StepsJenkins 9 | import com.daluobai.jenkinslib.steps.StepsWeb 10 | import com.daluobai.jenkinslib.utils.ConfigUtils 11 | import com.daluobai.jenkinslib.utils.MapUtils 12 | import com.daluobai.jenkinslib.utils.MessageUtils 13 | import com.daluobai.jenkinslib.utils.ConfigMergeUtils 14 | 15 | /** 16 | * @author daluobai@outlook.com 17 | * version 1.0.0 18 | * @title 19 | * @description https://github.com/daluobai-devops/jenkins-shared-library 20 | * @create 2023/4/25 12:10 21 | */ 22 | def call(Map customConfig) { 23 | 24 | /*******************初始化全局对象 开始*****************/ 25 | def stepsBuildNpm = new StepsBuildNpm(this) 26 | def stepsJenkins = new StepsJenkins(this) 27 | def stepsWeb = new StepsWeb(this) 28 | def messageUtils = new MessageUtils(this) 29 | def stepsGit = new StepsGit(this) 30 | /*******************初始化全局对象 结束*****************/ 31 | //用来运行构建的节点 32 | def nodeBuildNodeList = stepsJenkins.getNodeByLabel("buildNode") 33 | echo "获取到节点:${nodeBuildNodeList}" 34 | if (ObjUtils.isEmpty(nodeBuildNodeList)) { 35 | error '没有可用的构建节点' 36 | } 37 | /***初始化参数 开始**/ 38 | //错误信息 39 | def errMessage = "" 40 | EBuildStatusType eBuildStatusType = EBuildStatusType.FAILED 41 | //DEPLOY_PIPELINE顺序定义 42 | /***初始化参数 结束**/ 43 | //默认在同一个构建节点运行,如果需要在其他节点运行则单独写在node块中 44 | node(nodeBuildNodeList[0]) { 45 | try { 46 | //获取并合并配置 47 | stage("${it}") { 48 | echo "开始执行流程: ${it}" 49 | stepsGit.syncGit2Git(customConfig["orgGitUrl"], customConfig["orgCredentialsId"], customConfig["targetGitUrl"], customConfig["targetCredentialsId"]) 50 | } 51 | echo "结束执行流程: ${it}" 52 | eBuildStatusType = EBuildStatusType.SUCCESS 53 | } catch (Exception e) { 54 | if (e instanceof org.jenkinsci.plugins.workflow.steps.FlowInterruptedException) { 55 | eBuildStatusType = EBuildStatusType.ABORTED 56 | } else { 57 | eBuildStatusType = EBuildStatusType.FAILED 58 | errMessage = e.getMessage() 59 | } 60 | throw e 61 | } finally { 62 | if (ObjUtils.isNotEmpty(customConfig.SHARE_PARAM.message)) { 63 | def messageTitle = "" 64 | def messageContent = "" 65 | if (eBuildStatusType == EBuildStatusType.SUCCESS) { 66 | messageTitle = "成功:${customConfig.SHARE_PARAM.appName}" 67 | messageContent = "发布成功: ${currentBuild.fullDisplayName}" 68 | } else if (eBuildStatusType == EBuildStatusType.FAILED) { 69 | messageTitle = "失败:${customConfig.SHARE_PARAM.appName}" 70 | messageContent = "发布失败: ${currentBuild.fullDisplayName},异常信息: ${errMessage},构建日志:(${BUILD_URL}console)" 71 | } else if (eBuildStatusType == EBuildStatusType.ABORTED) { 72 | //发布终止 73 | } 74 | if (StrUtils.isNotBlank(messageTitle) && StrUtils.isNotBlank(messageContent)) { 75 | messageUtils.sendMessage(customConfig.SHARE_PARAM.message, messageTitle, messageContent) 76 | } 77 | } 78 | deleteDir() 79 | } 80 | } 81 | } 82 | 83 | //获取默认配置路径 84 | def defaultConfigPath(EFileReadType eConfigType) { 85 | AssertUtils.notNull(eConfigType, "配置类型为空") 86 | def configPath = null 87 | if (eConfigType == EFileReadType.HOST_PATH) { 88 | configPath = "/usr/local/workspace/config/jenkins-pipeline/jenkins-pipeline-config/config.json" 89 | } else if (eConfigType == EFileReadType.RESOURCES) { 90 | configPath = "config/config.json" 91 | } else { 92 | throw new Exception("暂无默认配置类型") 93 | } 94 | return configPath 95 | } 96 | 97 | //合并配置customConfig >> extendConfig >> defaultConfig = fullConfig 98 | def mergeConfig(Map customConfig) { 99 | 100 | def fullConfig = [:] 101 | def extendConfig = [:] 102 | def defaultConfig = [:] 103 | //读取默认配置文件 104 | defaultConfig = new ConfigUtils(this).readConfig(EFileReadType.RESOURCES, defaultConfigPath(EFileReadType.RESOURCES)) 105 | echo "customConfig: ${customConfig.toString()}" 106 | echo "defaultConfig: ${defaultConfig.toString()}" 107 | //读取继承配置文件 108 | if (ObjUtils.isNotEmpty(customConfig.CONFIG_EXTEND) && ObjUtils.isNotEmpty(EFileReadType.get(customConfig.CONFIG_EXTEND.configFullPath))) { 109 | extendConfig = new ConfigUtils(this).readConfigFromFullPath(customConfig.CONFIG_EXTEND.configFullPath) 110 | echo "extendConfig: ${extendConfig.toString()}" 111 | } 112 | //合并自定义配置 113 | fullConfig = MapUtils.merge([defaultConfig, extendConfig, customConfig]) 114 | //根据自定义构建参数,修改配置 115 | fullConfig = ConfigMergeUtils.mergeParams(fullConfig, params) 116 | echo "fullConfig merged: ${fullConfig}" 117 | return MapUtils.deepCopy(fullConfig) 118 | } 119 | 120 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/utils/AssertUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.utils 2 | 3 | /** 4 | * @author daluobai@outlook.com 5 | * version 1.0.0 6 | * @title 断言工具类 - 参考hutool Assert 7 | * @description https://github.com/daluobai-devops/jenkins-shared-library 8 | * @create 2025/12/13 9 | */ 10 | class AssertUtils implements Serializable { 11 | 12 | /** 13 | * 断言对象不为null 14 | * @param obj 被检查的对象 15 | * @param errorMsgTemplate 异常信息模板 16 | * @param params 异常信息参数 17 | * @throws IllegalArgumentException 如果对象为null 18 | */ 19 | static void notNull(Object obj, String errorMsgTemplate = "对象为null", Object... params) { 20 | if (obj == null) { 21 | throw new IllegalArgumentException(StrUtils.format(errorMsgTemplate, params)) 22 | } 23 | } 24 | 25 | /** 26 | * 断言字符串不为空白 27 | * @param text 被检查的字符串 28 | * @param errorMsgTemplate 异常信息模板 29 | * @param params 异常信息参数 30 | * @throws IllegalArgumentException 如果字符串为空白 31 | */ 32 | static void notBlank(CharSequence text, String errorMsgTemplate = "字符串为空白", Object... params) { 33 | if (StrUtils.isBlank(text)) { 34 | throw new IllegalArgumentException(StrUtils.format(errorMsgTemplate, params)) 35 | } 36 | } 37 | 38 | /** 39 | * 断言字符串不为空 40 | * @param text 被检查的字符串 41 | * @param errorMsgTemplate 异常信息模板 42 | * @param params 异常信息参数 43 | * @throws IllegalArgumentException 如果字符串为空 44 | */ 45 | static void notEmpty(CharSequence text, String errorMsgTemplate = "字符串为空", Object... params) { 46 | if (StrUtils.isEmpty(text)) { 47 | throw new IllegalArgumentException(StrUtils.format(errorMsgTemplate, params)) 48 | } 49 | } 50 | 51 | /** 52 | * 断言集合不为空 53 | * @param collection 被检查的集合 54 | * @param errorMsgTemplate 异常信息模板 55 | * @param params 异常信息参数 56 | * @throws IllegalArgumentException 如果集合为空 57 | */ 58 | static void notEmpty(Collection collection, String errorMsgTemplate = "集合为空", Object... params) { 59 | if (collection == null || collection.isEmpty()) { 60 | throw new IllegalArgumentException(StrUtils.format(errorMsgTemplate, params)) 61 | } 62 | } 63 | 64 | /** 65 | * 断言Map不为空 66 | * @param map 被检查的Map 67 | * @param errorMsgTemplate 异常信息模板 68 | * @param params 异常信息参数 69 | * @throws IllegalArgumentException 如果Map为空 70 | */ 71 | static void notEmpty(Map map, String errorMsgTemplate = "Map为空", Object... params) { 72 | if (map == null || map.isEmpty()) { 73 | throw new IllegalArgumentException(StrUtils.format(errorMsgTemplate, params)) 74 | } 75 | } 76 | 77 | /** 78 | * 断言数组不为空 79 | * @param array 被检查的数组 80 | * @param errorMsgTemplate 异常信息模板 81 | * @param params 异常信息参数 82 | * @throws IllegalArgumentException 如果数组为空 83 | */ 84 | static void notEmpty(Object[] array, String errorMsgTemplate = "数组为空", Object... params) { 85 | if (array == null || array.length == 0) { 86 | throw new IllegalArgumentException(StrUtils.format(errorMsgTemplate, params)) 87 | } 88 | } 89 | 90 | /** 91 | * 断言表达式为true 92 | * @param expression 表达式 93 | * @param errorMsgTemplate 异常信息模板 94 | * @param params 异常信息参数 95 | * @throws IllegalArgumentException 如果表达式为false 96 | */ 97 | static void isTrue(boolean expression, String errorMsgTemplate = "表达式为false", Object... params) { 98 | if (!expression) { 99 | throw new IllegalArgumentException(StrUtils.format(errorMsgTemplate, params)) 100 | } 101 | } 102 | 103 | /** 104 | * 断言表达式为false 105 | * @param expression 表达式 106 | * @param errorMsgTemplate 异常信息模板 107 | * @param params 异常信息参数 108 | * @throws IllegalArgumentException 如果表达式为true 109 | */ 110 | static void isFalse(boolean expression, String errorMsgTemplate = "表达式为true", Object... params) { 111 | if (expression) { 112 | throw new IllegalArgumentException(StrUtils.format(errorMsgTemplate, params)) 113 | } 114 | } 115 | 116 | /** 117 | * 断言两个对象相等 118 | * @param obj1 对象1 119 | * @param obj2 对象2 120 | * @param errorMsgTemplate 异常信息模板 121 | * @param params 异常信息参数 122 | * @throws IllegalArgumentException 如果两个对象不相等 123 | */ 124 | static void equals(Object obj1, Object obj2, String errorMsgTemplate = "对象不相等", Object... params) { 125 | if (obj1 == null) { 126 | if (obj2 != null) { 127 | throw new IllegalArgumentException(StrUtils.format(errorMsgTemplate, params)) 128 | } 129 | } else if (!obj1.equals(obj2)) { 130 | throw new IllegalArgumentException(StrUtils.format(errorMsgTemplate, params)) 131 | } 132 | } 133 | 134 | /** 135 | * 断言对象在指定范围内 136 | * @param value 值 137 | * @param min 最小值(包含) 138 | * @param max 最大值(包含) 139 | * @param errorMsgTemplate 异常信息模板 140 | * @param params 异常信息参数 141 | * @throws IllegalArgumentException 如果值不在范围内 142 | */ 143 | static void between(long value, long min, long max, String errorMsgTemplate = "值不在范围内", Object... params) { 144 | if (value < min || value > max) { 145 | throw new IllegalArgumentException(StrUtils.format(errorMsgTemplate, params)) 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/steps/StepsDeploy.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.steps 2 | 3 | @Grab('org.reflections:reflections:0.9.9-RC1') 4 | import com.daluobai.jenkinslib.utils.DateUtils 5 | import com.daluobai.jenkinslib.utils.AssertUtils 6 | import com.daluobai.jenkinslib.utils.ObjUtils 7 | import com.daluobai.jenkinslib.utils.StrUtils 8 | import com.daluobai.jenkinslib.constant.GlobalShare 9 | import com.daluobai.jenkinslib.utils.EndpointUtils 10 | 11 | /** 12 | * @author daluobai@outlook.com 13 | * version 1.0.0 14 | * @title 发布到tomcat 15 | * @description https://github.com/daluobai-devops/jenkins-shared-library 16 | * @create 2023/4/25 12:10 17 | */ 18 | class StepsDeploy implements Serializable { 19 | def steps 20 | 21 | StepsDeploy(steps) { this.steps = steps } 22 | 23 | /*******************初始化全局对象 开始*****************/ 24 | def stepsJenkins = new StepsJenkins(steps) 25 | def endpointUtils = new EndpointUtils(steps) 26 | def stepsTomcat = new StepsTomcat(steps) 27 | def stepsJavaWeb = new StepsJavaWeb(steps) 28 | /*******************初始化全局对象 结束*****************/ 29 | 30 | //发布 31 | def deploy(Map parameterMap) { 32 | steps.echo "StepsJavaWeb:${parameterMap}" 33 | AssertUtils.notEmpty(parameterMap, "参数为空") 34 | def labels = parameterMap.labels 35 | def enable = parameterMap.enable 36 | def readinessProbeMap = parameterMap.readinessProbe 37 | def afterRunCMD = parameterMap.afterRunCMD 38 | def globalParameterMap = steps.globalParameterMap 39 | def appName = globalParameterMap.SHARE_PARAM.appName 40 | def archiveName = globalParameterMap.SHARE_PARAM.archiveName 41 | //获取文件名后缀 42 | def archiveSuffix = StrUtils.subAfter(archiveName, ".", true) 43 | //获取文件名 44 | def archiveOnlyName = StrUtils.subBefore(archiveName, ".", true) 45 | AssertUtils.notEmpty(labels, "labels为空") 46 | 47 | def backAppName = "app-" + DateUtils.format(new Date(), "yyyyMMddHHmmss") + "." + archiveSuffix 48 | 49 | labels.each { c -> 50 | def label = c 51 | steps.echo "发布节点:${label}" 52 | def nodeDeployNodeList = stepsJenkins.getNodeByLabel(label) 53 | steps.echo "获取到发布节点:${nodeDeployNodeList}" 54 | if (ObjUtils.isEmpty(nodeDeployNodeList)) { 55 | steps.error '没有可用的发布节点' 56 | } 57 | nodeDeployNodeList.each { d -> 58 | def nodeDeployNode = d 59 | steps.echo "开始发布:${nodeDeployNode}" 60 | steps.node(nodeDeployNode) { 61 | if (ObjUtils.isNotEmpty(parameterMap.stepsJavaWebDeployToService)) { 62 | stepsJavaWeb.deploy(parameterMap.stepsJavaWebDeployToService) 63 | } else if (ObjUtils.isNotEmpty(parameterMap.stepsTomcatDeploy)) { 64 | stepsTomcat.deploy(parameterMap.stepsJavaWebDeployToTomcat) 65 | } 66 | //健康检查 67 | if (readinessProbeMap != null) { 68 | def healthAll = true 69 | if (healthAll && ObjUtils.isNotEmpty(readinessProbeMap.tcp) && (readinessProbeMap.tcp.enable == null || readinessProbeMap.tcp.enable)) { 70 | def healthCheck = endpointUtils.healthCheckWithLocalTCPPort(readinessProbeMap.tcp.port, readinessProbeMap.period, readinessProbeMap.failureThreshold) 71 | if (!healthCheck) { 72 | healthAll = false 73 | steps.echo "healthCheckWithLocalTCPPort,检查失败" 74 | } 75 | steps.echo "healthCheckWithLocalTCPPort结束,${healthCheck}" 76 | } 77 | if (healthAll && ObjUtils.isNotEmpty(readinessProbeMap.http) && (readinessProbeMap.http.enable == null || readinessProbeMap.http.enable)) { 78 | def healthCheck = endpointUtils.healthCheckWithHttp("http://localhost:${readinessProbeMap.http.port}${readinessProbeMap.http.path}", readinessProbeMap.http.timeout, readinessProbeMap.period, readinessProbeMap.failureThreshold) 79 | if (!healthCheck) { 80 | healthAll = false 81 | steps.echo "healthCheckWithHttp,检查失败" 82 | } 83 | steps.echo "healthCheckWithHttp结束,${healthCheck}" 84 | } 85 | if (healthAll && ObjUtils.isNotEmpty(readinessProbeMap.cmd) && (readinessProbeMap.cmd.enable == null || readinessProbeMap.cmd.enable)) { 86 | def healthCheck = endpointUtils.healthCheckWithCMD(readinessProbeMap.cmd.command, readinessProbeMap.cmd.timeout, readinessProbeMap.period, readinessProbeMap.failureThreshold) 87 | if (!healthCheck) { 88 | healthAll = false 89 | steps.echo "healthCheckWithCMD,检查失败" 90 | } 91 | steps.echo "healthCheckWithCMD结束,${healthCheck}" 92 | } 93 | if (!healthAll) { 94 | steps.error '服务未就绪' 95 | } 96 | } 97 | //所有部署流程执行完成后运行的命令 98 | if (StrUtils.isNotBlank(afterRunCMD)) { 99 | steps.sh "${afterRunCMD}" 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/steps/StepsJenkins.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.steps 2 | 3 | import com.daluobai.jenkinslib.utils.DateUtils 4 | import com.daluobai.jenkinslib.utils.AssertUtils 5 | import com.daluobai.jenkinslib.utils.ObjUtils 6 | import com.daluobai.jenkinslib.utils.StrUtils 7 | import com.daluobai.jenkinslib.constant.GlobalShare 8 | /** 9 | * @author daluobai@outlook.com 10 | * version 1.0.0 11 | * @title 12 | * @description https://github.com/daluobai-devops/jenkins-shared-library 13 | * @create 2023/4/25 12:10 14 | */ 15 | class StepsJenkins implements Serializable { 16 | def steps 17 | 18 | StepsJenkins(steps) { this.steps = steps } 19 | 20 | /*******************初始化全局对象 开始*****************/ 21 | def stepsGit = new StepsGit(steps) 22 | /*******************初始化全局对象 结束*****************/ 23 | 24 | /** 25 | * 存储 26 | * 27 | */ 28 | def stash(Map parameterMap) { 29 | AssertUtils.notEmpty(parameterMap,"参数为空") 30 | def archiveType = parameterMap.archiveType 31 | def jenkinsStash = parameterMap.jenkinsStash 32 | def archiveArtifacts = parameterMap.archiveArtifacts 33 | def dockerRegistry = parameterMap.dockerRegistry 34 | def dockerfile = parameterMap?.dockerRegistry?.dockerfile 35 | def fullConfig = steps.globalParameterMap 36 | AssertUtils.notBlank(archiveType,"archiveType为空") 37 | def includes 38 | def archiveName 39 | steps.sh "ls package -l" 40 | steps.sh "mkdir -p stash" 41 | 42 | //根据文件类型处理文件 43 | if (archiveType == "JAR") { 44 | archiveName = "app.jar" 45 | steps.sh "rm -f package/*-sources.jar" 46 | steps.sh "mv package/*.jar package/app.jar || true" 47 | includes = "package/app.jar" 48 | } else if (archiveType == "WAR") { 49 | archiveName = "app.war" 50 | steps.sh "mv package/*.war package/app.war || true" 51 | includes = "package/app.war" 52 | } else if (archiveType == "TAR") { 53 | archiveName = "app.tar.gz" 54 | steps.sh "mv package/*.tar.gz package/app.tar.gz || true" 55 | includes = "package/app.tar.gz" 56 | } 57 | else if (archiveType == "ZIP") { 58 | archiveName = "app.zip" 59 | steps.sh "mv package/*.zip package/app.zip || true" 60 | includes = "package/app.zip" 61 | } 62 | // else if (archiveType == "FOLDER"){ 63 | // archiveName = "app" 64 | // includes = "package/app/**/*" 65 | // } 66 | else { 67 | throw new Exception("archiveType不支持") 68 | } 69 | steps.sh "ls package -l" 70 | 71 | if (archiveArtifacts == true){ 72 | def jobName = steps.currentBuild.projectName 73 | //获取archiveName的后缀 74 | def archiveSuffix = archiveName.substring(archiveName.indexOf(".") + 1); 75 | def archiveArtifactName = "${steps.currentBuild.projectName}-${DateUtils.format(new Date(), "yyyyMMddHHmmss")}.${archiveSuffix}" 76 | steps.sh "\\cp -f ${includes} package/${archiveArtifactName} || true" 77 | steps.archiveArtifacts artifacts: "package/${archiveArtifactName}", followSymlinks: false 78 | } 79 | 80 | if (jenkinsStash?.enable) { 81 | steps.stash name: "appPackage", includes: "${includes}" 82 | } 83 | if (dockerRegistry?.enable) { 84 | steps.sh "mkdir -p stash/dockerRegistry/code/code" 85 | steps.dir("stash/dockerRegistry/code/code") { 86 | steps.git branch: "${dockerfile.gitBranch}", credentialsId: 'ssh-git', url: "${dockerfile.url}" 87 | } 88 | // steps.sh '''mv stash/dockerRegistry/code/\$(ls -A1 stash/dockerRegistry/code/) stash/dockerRegistry/code/code/''' 89 | //把构建的东西放到dockerfile目录下 90 | steps.sh "mkdir -p stash/dockerRegistry/code/code/${dockerfile.path}/build/package" 91 | steps.sh "cp -r ${includes} stash/dockerRegistry/code/code/${dockerfile.path}/build/package/" 92 | 93 | // 拼接 94 | def buildArgs = "" 95 | if (dockerRegistry.buildArgs != null && dockerRegistry.buildArgs.size() > 0) { 96 | dockerRegistry.buildArgs.each { key, value -> 97 | if (StrUtils.isNotBlank(value) && StrUtils.isNotBlank(key)){ 98 | buildArgs += "--build-arg \'${key}\'=\'${value}\' " 99 | } 100 | } 101 | } 102 | def imageName = StrUtils.isBlank(dockerRegistry.imageName) ? fullConfig.SHARE_PARAM.appName : dockerRegistry.imageName 103 | def imageVersion = StrUtils.isBlank(dockerRegistry.imageVersion) ? DateUtils.format(new Date(), "yyyyMMddHHmmss") : dockerRegistry.imageVersion 104 | steps.dir("stash/dockerRegistry/code/code/${dockerfile.path}") { 105 | steps.sh "ls -l" 106 | steps.sh "docker build ${buildArgs} -t ${dockerRegistry.imagePrefix}/${imageName}:${imageVersion} ." 107 | steps.sh "docker push ${dockerRegistry.imagePrefix}/${imageName}:${imageVersion}" 108 | archiveName = "${dockerRegistry.imagePrefix}/${imageName}:${imageVersion}" 109 | } 110 | } 111 | fullConfig.SHARE_PARAM.put("archiveName",archiveName) 112 | } 113 | 114 | /** 115 | * 根据label获取节点,如果为空则返回主节点 116 | * 117 | */ 118 | def getNodeByLabel(String label) { 119 | AssertUtils.notBlank(label,"label为空") 120 | def nodeBuildNodeList = steps.nodesByLabel label: label 121 | if (nodeBuildNodeList != null && nodeBuildNodeList.size() > 0) { 122 | //这里因为如果是 master 节点返回的数组是空字符串,所以这里需要判断一下 123 | for (i in 0..< nodeBuildNodeList.size()) { 124 | if (StrUtils.isBlank(nodeBuildNodeList[i])){ 125 | nodeBuildNodeList[i] = "master" 126 | } 127 | } 128 | } 129 | 130 | return (nodeBuildNodeList).toList() 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/steps/StepsJavaWeb.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.steps 2 | 3 | import com.daluobai.jenkinslib.utils.DateUtils 4 | import com.daluobai.jenkinslib.utils.IoUtils 5 | import com.daluobai.jenkinslib.utils.AssertUtils 6 | import com.daluobai.jenkinslib.utils.ObjUtils 7 | import com.daluobai.jenkinslib.utils.StrUtils 8 | import com.daluobai.jenkinslib.constant.GlobalShare 9 | import com.daluobai.jenkinslib.utils.EndpointUtils 10 | import com.daluobai.jenkinslib.utils.JenkinsUtils 11 | import com.daluobai.jenkinslib.utils.TemplateUtils 12 | /** 13 | * @author daluobai@outlook.com 14 | * version 1.0.0 15 | * @title 16 | * @description https://github.com/daluobai-devops/jenkins-shared-library 17 | * @create 2023/4/25 12:10 18 | */ 19 | class StepsJavaWeb implements Serializable { 20 | def steps 21 | 22 | StepsJavaWeb(steps) { this.steps = steps } 23 | 24 | /*******************初始化全局对象 开始*****************/ 25 | def stepsJenkins = new StepsJenkins(steps) 26 | def endpointUtils = new EndpointUtils(steps) 27 | def jenkinsUtils = new JenkinsUtils(steps) 28 | /*******************初始化全局对象 结束*****************/ 29 | 30 | //发布 31 | def deploy(Map parameterMap) { 32 | steps.echo "StepsJavaWeb:${parameterMap}" 33 | AssertUtils.notEmpty(parameterMap,"参数为空") 34 | def pathRoot = parameterMap.pathRoot 35 | def globalParameterMap = steps.globalParameterMap 36 | def appName = globalParameterMap.SHARE_PARAM.appName 37 | def archiveName = globalParameterMap.SHARE_PARAM.archiveName 38 | //获取文件名后缀 39 | def archiveSuffix = StrUtils.subAfter(archiveName, ".", true) 40 | 41 | def backAppName = "app-" + DateUtils.format(new Date(), "yyyyMMddHHmmss") + "." + archiveSuffix 42 | // steps.withCredentials([steps.sshUserPrivateKey(credentialsId: 'ssh-jenkins', keyFileVariable: 'SSH_KEY_PATH')]) { 43 | // steps.sh "mkdir -p ~/.ssh && chmod 700 ~/.ssh && rm -f ~/.ssh/id_rsa && cp \${SSH_KEY_PATH} ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa" 44 | // } 45 | steps.unstash("appPackage") 46 | steps.sh "hostname" 47 | steps.sh "ls -l package" 48 | steps.sh "mkdir -p ${pathRoot}/${appName} && mkdir -p ${pathRoot}/${appName}/backup" 49 | //备份 50 | steps.sh "mv ${pathRoot}/${appName}/${archiveName} ${pathRoot}/${appName}/backup/${backAppName} || true" 51 | steps.dir("${pathRoot}/${appName}/backup/"){ 52 | steps.sh "find . -mtime +3 -delete" 53 | } 54 | //拷贝新的包到发布目录 55 | steps.sh "\\cp -f package/${archiveName} ${pathRoot}/${appName}" 56 | if (parameterMap.manageBy){ 57 | 58 | } 59 | //判断用哪种方式管理服务 60 | def manageBySystemctl = StrUtils.isBlank(parameterMap.manageBy) || parameterMap.manageBy == "systemctl" 61 | //判断是否有systemctl命令,返回0表示有,返回1表示没有 62 | def systemctlRe = steps.sh returnStatus: true, script: 'command -v systemctl' 63 | if (manageBySystemctl && systemctlRe == 0) { 64 | steps.echo "通过systemctl重启" 65 | //systemctl重启 66 | reStartBySystemctl(parameterMap) 67 | } else { 68 | steps.echo "通过shell重启" 69 | reStartByShell(parameterMap) 70 | } 71 | } 72 | 73 | /** 74 | * Systemctl重启 75 | * @param parameterMap 76 | * @return 77 | */ 78 | def reStartBySystemctl(Map parameterMap){ 79 | AssertUtils.notEmpty(parameterMap,"参数为空") 80 | def globalParameterMap = steps.globalParameterMap 81 | def appName = globalParameterMap.SHARE_PARAM.appName 82 | def archiveName = globalParameterMap.SHARE_PARAM.archiveName 83 | def labels = parameterMap.labels 84 | def pathRoot = parameterMap.pathRoot 85 | def javaPath = ObjUtils.isEmpty(parameterMap.javaPath) ? "/usr/local/bin/java" : parameterMap.javaPath 86 | //获取登录的用户 87 | def loginUser= jenkinsUtils.pipelineSH("whoami") 88 | steps.echo "当前登录用户:${loginUser}" 89 | 90 | //生成服务文件 91 | steps.sh "systemctl stop ${appName}.service || true" 92 | steps.sh "rm -f /etc/systemd/systemO/${appName}.service || true" 93 | def serviceTemplate = steps.libraryResource 'template/service/JavaWeb.service' 94 | def templateData = [ 95 | javaPath: javaPath, 96 | runOptions: parameterMap.runOptions, 97 | pathRoot: parameterMap.pathRoot, 98 | appName: appName, 99 | archiveName: archiveName, 100 | runArgs: parameterMap.runArgs 101 | ] 102 | steps.writeFile file: "/etc/systemd/system/${appName}.service", text: TemplateUtils.makeTemplate(serviceTemplate,templateData) 103 | steps.sh "systemctl daemon-reload" 104 | //切换到发布目录 105 | steps.dir("${pathRoot}/${appName}"){ 106 | steps.sh "systemctl enable ${appName}.service" 107 | steps.sh "systemctl start ${appName}.service" 108 | } 109 | } 110 | 111 | def reStartByShell(Map parameterMap){ 112 | AssertUtils.notEmpty(parameterMap,"参数为空") 113 | def globalParameterMap = steps.globalParameterMap 114 | def appName = globalParameterMap.SHARE_PARAM.appName 115 | def archiveName = globalParameterMap.SHARE_PARAM.archiveName 116 | def labels = parameterMap.labels 117 | def pathRoot = parameterMap.pathRoot 118 | def javaPath = ObjUtils.isEmpty(parameterMap.javaPath) ? "/usr/local/bin/java" : parameterMap.javaPath 119 | def shellPath = "${pathRoot}/${appName}/service.sh" 120 | //先删掉原来的脚本文件 121 | steps.sh "rm -f ${shellPath} || true" 122 | //生成脚本文件 123 | def serviceTemplate = steps.libraryResource 'template/shell/javaWeb/service.sh' 124 | def templateData = [ 125 | javaPath: javaPath, 126 | runOptions: parameterMap.runOptions, 127 | pathRoot: parameterMap.pathRoot, 128 | appName: appName, 129 | archiveName: archiveName, 130 | runArgs: parameterMap.runArgs 131 | ] 132 | steps.writeFile file: "${shellPath}", text: TemplateUtils.makeTemplate(serviceTemplate,templateData) 133 | steps.sh "chmod +x ${shellPath}" 134 | steps.dir("${pathRoot}/${appName}"){ 135 | steps.withEnv(["JENKINS_NODE_COOKIE=dontKillMe"]) { 136 | steps.sh "$shellPath restart" 137 | } 138 | } 139 | 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /vars/deployWeb.groovy: -------------------------------------------------------------------------------- 1 | import com.daluobai.jenkinslib.utils.AssertUtils 2 | import com.daluobai.jenkinslib.utils.StrUtils 3 | import com.daluobai.jenkinslib.utils.ObjUtils 4 | import com.daluobai.jenkinslib.constant.EBuildStatusType 5 | import com.daluobai.jenkinslib.constant.EFileReadType 6 | import com.daluobai.jenkinslib.steps.StepsBuildNpm 7 | import com.daluobai.jenkinslib.steps.StepsJenkins 8 | import com.daluobai.jenkinslib.steps.StepsJavaWeb 9 | import com.daluobai.jenkinslib.steps.StepsWeb 10 | import com.daluobai.jenkinslib.utils.ConfigUtils 11 | import com.daluobai.jenkinslib.utils.MapUtils 12 | import com.daluobai.jenkinslib.utils.MessageUtils 13 | import com.daluobai.jenkinslib.utils.ConfigMergeUtils 14 | import groovy.transform.Field 15 | 16 | @Field Map globalParameterMap = [:] 17 | 18 | /** 19 | * @author daluobai@outlook.com 20 | * version 1.0.0 21 | * @title 22 | * @description https://github.com/daluobai-devops/jenkins-shared-library 23 | * @create 2023/4/25 12:10 24 | */ 25 | def call(Map customConfig) { 26 | 27 | /*******************初始化全局对象 开始*****************/ 28 | def stepsBuildNpm = new StepsBuildNpm(this) 29 | def stepsJenkins = new StepsJenkins(this) 30 | def stepsWeb = new StepsWeb(this) 31 | def messageUtils = new MessageUtils(this) 32 | /*******************初始化全局对象 结束*****************/ 33 | //用来运行构建的节点 34 | def nodeBuildNodeList = stepsJenkins.getNodeByLabel("buildNode") 35 | echo "获取到节点:${nodeBuildNodeList}" 36 | if (ObjUtils.isEmpty(nodeBuildNodeList)) { 37 | error '没有可用的构建节点' 38 | } 39 | /***初始化参数 开始**/ 40 | //错误信息 41 | def errMessage = "" 42 | EBuildStatusType eBuildStatusType = EBuildStatusType.FAILED 43 | //DEPLOY_PIPELINE顺序定义 44 | def deployPipelineIndex = ["stepsBuildNpm","stepsStorage","stepsJavaWebDeployToWebServer"] 45 | //如果没传项目名称,则使用jenkins项目名称 46 | if (StrUtils.isBlank(customConfig.SHARE_PARAM.appName)){ 47 | customConfig.SHARE_PARAM.appName = currentBuild.projectName 48 | } 49 | def SHARE_PARAM = customConfig.SHARE_PARAM 50 | /***初始化参数 结束**/ 51 | //默认在同一个构建节点运行,如果需要在其他节点运行则单独写在node块中 52 | node(nodeBuildNodeList[0]) { 53 | try { 54 | //获取并合并配置 55 | def fullConfig = mergeConfig(customConfig) 56 | echo "fullConfig: ${fullConfig.toString()}" 57 | //设置共享参数。 58 | globalParameterMap = fullConfig 59 | messageUtils.sendMessage(false,customConfig.SHARE_PARAM.message, "发布开始:${customConfig.SHARE_PARAM.appName}", "发布开始: ${currentBuild.fullDisplayName}") 60 | //执行流程 61 | deployPipelineIndex.each { 62 | stage("${it}") { 63 | def pipelineConfigItemMap = fullConfig.DEPLOY_PIPELINE[it] 64 | if (pipelineConfigItemMap["enable"] != null && pipelineConfigItemMap["enable"] == false) { 65 | echo "跳过流程: ${it}" 66 | return 67 | } 68 | echo "开始执行流程: ${it}" 69 | if (it == "stepsBuildNpm") { 70 | stepsBuildNpm.build(fullConfig) 71 | } else if (it == "stepsStorage") { 72 | stepsJenkins.stash(pipelineConfigItemMap) 73 | } else if (it == "stepsJavaWebDeployToWebServer") { 74 | stepsWeb.deploy(pipelineConfigItemMap) 75 | } 76 | } 77 | echo "结束执行流程: ${it}" 78 | } 79 | eBuildStatusType = EBuildStatusType.SUCCESS 80 | } catch (Exception e) { 81 | if (e instanceof org.jenkinsci.plugins.workflow.steps.FlowInterruptedException) { 82 | eBuildStatusType = EBuildStatusType.ABORTED 83 | } else { 84 | eBuildStatusType = EBuildStatusType.FAILED 85 | errMessage = e.getMessage() 86 | } 87 | throw e 88 | } finally { 89 | if (ObjUtils.isNotEmpty(customConfig.SHARE_PARAM.message)) { 90 | def messageTitle = "" 91 | def messageContent = "" 92 | if (eBuildStatusType == EBuildStatusType.SUCCESS) { 93 | messageTitle = "成功:${customConfig.SHARE_PARAM.appName}" 94 | messageContent = "发布成功: ${currentBuild.fullDisplayName}" 95 | } else if (eBuildStatusType == EBuildStatusType.FAILED) { 96 | messageTitle = "失败:${customConfig.SHARE_PARAM.appName}" 97 | messageContent = "发布失败: ${currentBuild.fullDisplayName},异常信息: ${errMessage},构建日志:(${BUILD_URL}console)" 98 | } else if (eBuildStatusType == EBuildStatusType.ABORTED) { 99 | //发布终止 100 | } 101 | if (StrUtils.isNotBlank(messageTitle) && StrUtils.isNotBlank(messageContent)) { 102 | messageUtils.sendMessage(customConfig.SHARE_PARAM.message, messageTitle, messageContent) 103 | } 104 | } 105 | deleteDir() 106 | } 107 | } 108 | } 109 | 110 | //获取默认配置路径 111 | def defaultConfigPath(EFileReadType eConfigType) { 112 | AssertUtils.notNull(eConfigType, "配置类型为空") 113 | def configPath = null 114 | if (eConfigType == EFileReadType.HOST_PATH) { 115 | configPath = "/usr/local/workspace/config/jenkins-pipeline/jenkins-pipeline-config/config.json" 116 | } else if (eConfigType == EFileReadType.RESOURCES) { 117 | configPath = "config/config.json" 118 | } else { 119 | throw new Exception("暂无默认配置类型") 120 | } 121 | return configPath 122 | } 123 | 124 | //合并配置customConfig >> extendConfig >> defaultConfig = fullConfig 125 | def mergeConfig(Map customConfig) { 126 | 127 | def fullConfig = [:] 128 | def extendConfig = [:] 129 | def defaultConfig = [:] 130 | //读取默认配置文件 131 | defaultConfig = new ConfigUtils(this).readConfig(EFileReadType.RESOURCES, defaultConfigPath(EFileReadType.RESOURCES)) 132 | echo "customConfig: ${customConfig.toString()}" 133 | echo "defaultConfig: ${defaultConfig.toString()}" 134 | //读取继承配置文件 135 | if (ObjUtils.isNotEmpty(customConfig.CONFIG_EXTEND) && ObjUtils.isNotEmpty(EFileReadType.get(customConfig.CONFIG_EXTEND.configFullPath))) { 136 | extendConfig = new ConfigUtils(this).readConfigFromFullPath(customConfig.CONFIG_EXTEND.configFullPath) 137 | echo "extendConfig: ${extendConfig.toString()}" 138 | } 139 | //合并自定义配置 140 | fullConfig = MapUtils.merge([defaultConfig, extendConfig, customConfig]) 141 | 142 | //根据自定义构建参数,修改配置 143 | fullConfig = ConfigMergeUtils.mergeParams(fullConfig, params) 144 | echo "fullConfig merged: ${fullConfig}" 145 | 146 | return MapUtils.deepCopy(fullConfig) 147 | } 148 | 149 | 150 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/steps/StepsGit.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.steps 2 | 3 | import com.daluobai.jenkinslib.utils.AssertUtils 4 | import com.daluobai.jenkinslib.utils.NumberUtils 5 | import com.daluobai.jenkinslib.utils.StrUtils 6 | import com.cloudbees.groovy.cps.NonCPS 7 | 8 | /** 9 | * @author daluobai@outlook.com 10 | * version 1.0.0 11 | * @title 12 | * @description https://github.com/daluobai-devops/jenkins-shared-library 13 | * @create 2023/4/25 12:10 14 | */ 15 | class StepsGit implements Serializable { 16 | def steps 17 | 18 | StepsGit(steps) { this.steps = steps } 19 | 20 | // git@127.21.8.1:2200/test/test1.git 21 | // git@github.com:xxxxx/test.git 22 | // ssh://git@192.168.11.11:10022/root/wuzhao-docker-compose.git 23 | // https://github.com/xxxxxx/test.git 24 | // http://127.21.8.1:80/test/test1.git 25 | 26 | 27 | /** 28 | * ssh-keyscan生成到known_hosts 29 | * @param url 30 | * @param path 路径,一般是~/.ssh/known_hosts 31 | * @return 32 | */ 33 | // @NonCPS 34 | def sshKeyscan(String gitUrl, String filePath) { 35 | // def domainByUrl = this.getDomainByGitUrl(gitUrl) 36 | // steps.echo "domainByUrl:${domainByUrl}" 37 | // Assert.notBlank(domainByUrl, "链接为空") 38 | def domainHostAndPortMap = getDomainHostAndPort(gitUrl) 39 | //获取到域名和端口 40 | if (domainHostAndPortMap != null) { 41 | def host = domainHostAndPortMap.host 42 | def portStr = domainHostAndPortMap.portStr 43 | if (StrUtils.isNotBlank(portStr)){ 44 | portStr = "-p ${portStr}" 45 | } 46 | steps.echo "ssh-keyscan:端口:${portStr} 地址:${host} >> ${filePath}" 47 | steps.sh """ 48 | #! /bin/sh -e 49 | mkdir -p \$(dirname $filePath) && touch ${filePath} 50 | chmod 700 \$(dirname $filePath) && chmod 600 ${filePath} 51 | ssh-keyscan ${portStr} ${host} >> ${filePath} 52 | """ 53 | } else { 54 | steps.error "链接格式不正确" 55 | } 56 | } 57 | 58 | //从git地址中获取host和port 59 | // @NonCPS 60 | def getDomainHostAndPort(String url) { 61 | // 定义正则表达式 62 | def pattern = ~/(?:(?:ssh|http(?:s)?):\/\/)?(?:git@)?([a-zA-Z0-9.\-]+)(?::(\d{2,}))?/ 63 | 64 | // 循环处理每个连接 65 | def matcher = pattern.matcher(url) 66 | if (matcher.find()) { 67 | def host = matcher.group(1) // 提取 host 68 | def port = matcher.group(2) // 提取 port,默认为 default 69 | String portStr = ""; 70 | if (StrUtils.isNotBlank(port) && NumberUtils.isNumber(port)) { 71 | portStr = String.valueOf(port) 72 | } 73 | steps.echo "Host: ${host}, Port: ${portStr}" 74 | return ["host":host,"portStr":portStr] 75 | } else { 76 | return null 77 | } 78 | } //从git地址中获取host和port 79 | // @NonCPS 80 | static 81 | def getDomainHostAndPort1(String url) { 82 | def pattern = /(?:(?:ssh|https?|git):\/\/(?:[^@]+@)?)?([a-zA-Z0-9.-]+)(?::([0-9]+))?/ 83 | 84 | def matcher = url =~ pattern 85 | if (matcher) { 86 | def host = matcher[0][1] // 捕获组 1:主机名/IP 87 | def port = matcher[0][2] // 捕获组 2:端口号(如果有) 88 | String portStr = ""; 89 | if (StrUtils.isNotBlank(port) && NumberUtils.isNumber(port)) { 90 | portStr = String.valueOf(port) 91 | } 92 | 93 | return ["host":host,"portStr":portStr] 94 | } else { 95 | return null 96 | } 97 | } 98 | 99 | /** 100 | * 保存ssh-key到~/.ssh/.ssh目录 101 | * @param credentialsId 102 | * @return 103 | */ 104 | def saveJenkinsSSHKey(String credentialsId, String path = '~/.ssh') { 105 | AssertUtils.notBlank(credentialsId, "credentialsId为空") 106 | steps.withCredentials([steps.sshUserPrivateKey(credentialsId: "${credentialsId}", keyFileVariable: 'SSH_KEY_PATH')]) { 107 | steps.sh "cat /etc/hostname && pwd && mkdir -p ${path} && chmod 700 ${path} && rm -f ${path}/id_rsa && cp \${SSH_KEY_PATH} ${path}/id_rsa || true && chmod 600 ${path}/id_rsa" 108 | } 109 | } 110 | 111 | /** 112 | * 从链接中获取域名 113 | * @param gitUrl 114 | * @return 115 | */ 116 | // @NonCPS 117 | // def getDomainByGitUrl(String gitUrl) { 118 | // def pattern = /(?<=@|:\/\/)([^\/:]+)/ 119 | // def matcher = (gitUrl =~ pattern) 120 | // def domain = "" 121 | // if (matcher.find()) { 122 | // domain = matcher.group(0) 123 | // } 124 | // return domain 125 | // } 126 | 127 | /** 128 | * 同步git仓库 129 | * @param orgGitUrl 130 | * @param orgCredentialsId 131 | * @param targetGitUrl 132 | * @param targetCredentialsId 133 | * @return 134 | */ 135 | def syncGit2Git(String orgGitUrl, String orgCredentialsId, String targetGitUrl, String targetCredentialsId) { 136 | def pathBase = "${steps.env.WORKSPACE}" 137 | //docker-构建产物目录 138 | def pathPackage = "package" 139 | //docker-代码目录 140 | def pathCode = "code" 141 | //存放临时sshkey的目录 142 | def pathSSHKey = "sshkey" 143 | //从 jenkins 凭据管理中获取密钥文件路径并且拷贝到工作目录下的ssh-git目录,后面clone的时候指定密钥为这个 144 | this.saveJenkinsSSHKey(orgCredentialsId, "${steps.env.WORKSPACE}/${pathSSHKey}/ssh-git-org") 145 | this.saveJenkinsSSHKey(targetCredentialsId, "${steps.env.WORKSPACE}/${pathSSHKey}/ssh-git-target") 146 | //生成known_hosts 147 | this.sshKeyscan("${orgGitUrl}", "~/.ssh/known_hosts") 148 | this.sshKeyscan("${targetGitUrl}", "~/.ssh/known_hosts") 149 | steps.sh """ 150 | #! /bin/sh -e 151 | mkdir -p ${pathBase}/${pathPackage} && mkdir -p ${pathBase}/${pathCode} 152 | cd ${pathBase}/${pathCode} 153 | git config --global http.version HTTP/1.1 154 | GIT_SSH_COMMAND='ssh -i ${steps.env.WORKSPACE}/${pathSSHKey}/ssh-git-org/id_rsa' git clone ${orgGitUrl} --quiet 155 | mv ${pathBase}/${pathCode}/\$(ls -A1 ${pathBase}/${pathCode}/) ${pathBase}/${pathCode}/${pathCode} 156 | cd ${pathBase}/${pathCode}/${pathCode} 157 | git log --pretty=format:"%h -%an,%ar : %s" -1 158 | git config core.ignorecase false 159 | ls -al ${pathBase}/${pathCode}/${pathCode}/ 160 | git remote remove origin_target 2>/dev/null || true 161 | git remote add origin_target "${targetGitUrl}" 162 | GIT_SSH_COMMAND='ssh -i ${steps.env.WORKSPACE}/${pathSSHKey}/ssh-git-target/id_rsa' git push origin_target --quiet 163 | """ 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /configdemo/deploy.groovy.bak: -------------------------------------------------------------------------------- 1 | //未来版本规划 2 | def customConfig = [ 3 | //公共参数 4 | "SHARE_PARAM" : [ 5 | //app 名称,如果没填则使用jenkins job名称。可选 6 | "appName" : "test", 7 | //消息通知,可选 8 | "message": [ 9 | //企业微信通知 可选 10 | "wecom": [ 11 | //企业微信机器人token 必填 12 | "key": "" 13 | ] 14 | ] 15 | ], 16 | //发布流程 17 | "DEPLOY_PIPELINE": [ 18 | //构建 19 | "stepsBuild":[ 20 | //是否激活,默认true 21 | "enable" : true, 22 | //通过maven构建 23 | "stepsBuildMaven" : [ 24 | //是否激活,默认true 25 | "enable" : true, 26 | //app git url 必填. 27 | "gitUrl" : "git@gitee.com:renrenio/renren-security.git", 28 | //git 分支 29 | "gitBranch" : "master", 30 | //子模块目录,如果要构建子模块填入子模块目录,如果没有不填 可选 31 | "subModule" : "renren-api/", 32 | //是否跳过测试 可选 33 | "skipTest" : true, 34 | //生命周期 必填 35 | "lifecycle" : "clean package", 36 | //settings.xml文件路径,支持URL,HOST_PATH,RESOURCES 可选 37 | "settingsFullPath": "RESOURCES:config/settings.xml", 38 | //用来打包的镜像 可选 39 | "dockerBuildImage": "registry.cn-hangzhou.aliyuncs.com/wuzhaozhongguo/build-maven:3-jdk17", 40 | //激活的profile,maven -P参数 可选 41 | "activeProfile" : "dev" 42 | ] 43 | ], 44 | //存储 45 | "stepsStorage" : [ 46 | //是否激活,默认true 47 | "enable" : true, 48 | //构建产物类型 JAR,WAR,ZIP 必填 49 | "archiveType" : "JAR", 50 | //存储类型 jenkinsStash,dockerRegistry 必填 51 | "jenkinsStash" : [ 52 | //是否激活,默认false 53 | "enable": true, 54 | ], 55 | "dockerRegistry": [ 56 | //是否激活,默认false 57 | "enable" : false, 58 | "imagePrefix" : "registry.cn-hangzhou.aliyuncs.com/wuzhaozhongguo-app/", 59 | "dockerfile": [ 60 | "url" : "git@github.com:daluobai-devops/docker-library.git", 61 | "path": "package-javaweb/openjdk8" 62 | ], 63 | ], 64 | 65 | ], 66 | //发布 67 | "stepsDeploy": [ 68 | //是否激活,默认true 69 | "enable" : true, 70 | //服务发布服务label 必填 71 | "labels" : ["NODE-DEMO"], 72 | //发布java web服务 73 | "stepsJavaWebDeployToService": [ 74 | //是否激活,默认true 75 | "enable" : true, 76 | //java路径 可选 77 | "javaPath" : "/usr/local/jdk/jdk17/bin/java", 78 | //服务发布路径 必填 79 | "pathRoot" : "/apps/application/", 80 | //启动参数 [-options] 示例(-Dfile.encoding=UTF-8 -Xms128M -Xmx128M -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005) 81 | "runOptions": "-Xms128M -Xmx128M", 82 | //启动参数 [args...] 示例(-–spring.profiles.active=dev) 83 | "runArgs" : "--spring.profiles.active=dev" 84 | ], 85 | //就绪探针 可选,检查服务是否启动成功,如果启动成功则认为服务发布成功,如果不填则不检查.探针类型,支持http,tcp,cmd. 86 | "readinessProbe" : [ 87 | //检查端口是否监听,如果监听则认为发布成功,如果不填则不检查 可选 88 | tcp: [ 89 | //是否激活,默认true 90 | "enable" : true, 91 | //探针端口 92 | "port" : 8080 93 | ], 94 | //访问http地址,http状态码返回200则认为发布成功,如果不填则不检查 可选 95 | http: [ 96 | //是否激活,默认true 97 | "enable" : false, 98 | //探针路径, 必填 99 | "path" : "/actuator/health", 100 | //探针端口, 必填 101 | "port" : 8080, 102 | //探针超时时间,单位秒,默认10秒 可选 103 | "timeout": 10 104 | ], 105 | //执行命令,以退出状态码判断是否成功 可选 106 | cmd: [ 107 | //是否激活,默认true 108 | "enable" : false, 109 | //探针命令,如果type为cmd则必填 必填 110 | "command": "curl -s -o /dev/null -w %{http_code} http://localhost:8080/actuator/health", 111 | //探针超时时间,单位秒,默认10秒 可选 112 | "timeout": 10 113 | ], 114 | //探针间隔时间,单位秒,默认5秒 可选 115 | "period" : 5, 116 | //探针失败次数,如果失败次数达到该值则认为发布失败,默认3次 可选 117 | "failureThreshold": 20 118 | ], 119 | ] 120 | ], 121 | //默认配置 122 | "DEFAULT_CONFIG" : [ 123 | "docker": [ 124 | "registry": [ 125 | "domain": "docker.io" 126 | ] 127 | ] 128 | ], 129 | // //继承配置 130 | "CONFIG_EXTEND" : [ 131 | //配置文件完整路径configType:path,支持URL,HOST_PATH,RESOURCES,默认RESOURCES. 必填. 132 | "configFullPath": "RESOURCES:config/config.json", 133 | ] 134 | ] 135 | @Library('jenkins-shared-library') _ 136 | deployJavaWeb(customConfig) -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/utils/EndpointUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.utils 2 | 3 | import com.daluobai.jenkinslib.utils.ObjUtils 4 | import com.daluobai.jenkinslib.utils.HttpUtils 5 | import com.daluobai.jenkinslib.utils.AssertUtils 6 | import com.daluobai.jenkinslib.utils.JsonUtils 7 | import com.daluobai.jenkinslib.utils.StrUtils 8 | /** 9 | * @author daluobai@outlook.com 10 | * version 1.0.0 11 | * @title 12 | * @description https://github.com/daluobai-devops/jenkins-shared-library 13 | * @create 2023/4/25 12:10 14 | */ 15 | class EndpointUtils implements Serializable { 16 | def steps 17 | 18 | EndpointUtils(steps) { this.steps = steps } 19 | 20 | /** 21 | * 健康检查 重试60次,每次等待3s. 22 | * @param heathcheckUrl 健康检查的链接 23 | * @return 24 | */ 25 | def healthCheck(String heathcheckUrl) { 26 | AssertUtils.notBlank(heathcheckUrl,"heathcheckUrl为空"); 27 | steps.echo "健康检查-路径:${heathcheckUrl}" 28 | boolean isOnline = false 29 | for (int i = 0; i < 60; i++) { 30 | // steps.echo "健康检查-第${i}次" 31 | sleep 3000 32 | String response = ""; 33 | try { 34 | response = HttpUtils.get(heathcheckUrl) 35 | } catch (Exception e) { 36 | 37 | } 38 | if (StrUtils.isBlank(response) || !JsonUtils.isJson(response)){ 39 | continue 40 | } 41 | def responseJson = JsonUtils.parseObj(response); 42 | 43 | String status = responseJson.getStr("status"); 44 | if (StrUtils.isNotBlank(status) && status.equals("UP")){ 45 | isOnline = true 46 | break 47 | } 48 | } 49 | 50 | return isOnline; 51 | } 52 | 53 | /** 54 | * 健康检查 重试60次,每次等待3s. 55 | * @param deployName 56 | * @param namespace 57 | * @return 58 | */ 59 | def kubernetesDeployStatusCheck(String deployName,String namespace) { 60 | AssertUtils.notBlank(deployName,"deployName空的"); 61 | AssertUtils.notBlank(namespace,"namespace空的"); 62 | def kubernetesApi = new com.daluobai.jenkinslib.api.KubernetesApi(steps) 63 | steps.echo "发布状态检查:${deployName} ${namespace}" 64 | boolean isOnline = false 65 | for (int i = 0; i < 60; i++) { 66 | // steps.echo "发布状态检查-第${i}次" 67 | sleep 3000 68 | def deployStatusMap = kubernetesApi.deploymentStatus(deployName,namespace) 69 | steps.echo "发布状态检查:${deployStatusMap}" 70 | if (deployStatusMap == null){ 71 | continue 72 | } 73 | int replicas = deployStatusMap.getStr("replicas"); 74 | int readyReplicas = deployStatusMap.getStr("readyReplicas"); 75 | if (replicas > 0 && replicas == readyReplicas){ 76 | isOnline = true 77 | break 78 | } 79 | } 80 | 81 | return isOnline; 82 | } 83 | 84 | /** 85 | * 检查本地端口是否监听 86 | * @param localTCPPort 87 | * @return 88 | */ 89 | def healthCheckWithLocalTCPPort(def localTCPPort,def periodSec,def failureThreshold) { 90 | AssertUtils.notNull(localTCPPort,"端口为空") 91 | steps.echo "检查本地端口是否监听-参数${localTCPPort},间隔${periodSec},重试次数${failureThreshold}" 92 | if (ObjUtils.isNull(periodSec) || periodSec < 0){ 93 | periodSec = 0 94 | } 95 | if (ObjUtils.isNull(failureThreshold) || failureThreshold < 1){ 96 | failureThreshold = 1 97 | } 98 | def periodMS = periodSec * 1000 99 | steps.echo "检查本地端口是否监听:${localTCPPort}" 100 | boolean isOnline = false 101 | for (int i = 0; i < failureThreshold; i++) { 102 | // steps.echo "健康检查-第${i}次" 103 | sleep periodMS 104 | //加上wc -l会导致结果不对,所以按照是否有返回值判断 105 | def portListeningStr = steps.sh returnStdout: true, script: """ss -tuln | egrep '^.*${localTCPPort}\\s' | awk '\$1 ~ /tcp/ && \$2 == "LISTEN" {print \$0}'""" 106 | boolean portListening = ObjUtils.isNotEmpty(portListeningStr) && ObjUtils.isNotEmpty(portListeningStr.trim()) 107 | if (portListening){ 108 | steps.echo "端口监听成功:${portListeningStr},${localTCPPort}" 109 | isOnline = true 110 | break 111 | } 112 | } 113 | return isOnline; 114 | } 115 | 116 | /** 117 | * 检查http接口是否正常 118 | * @param 119 | * @return 120 | */ 121 | def healthCheckWithHttp(def url,def timeout,def periodSec,def failureThreshold) { 122 | AssertUtils.notNull(url,"url为空") 123 | steps.echo "检查http是能访问-参数${url},${timeout},间隔${periodSec},重试次数${failureThreshold}" 124 | if (ObjUtils.isNull(periodSec) || periodSec < 0){ 125 | periodSec = 0 126 | } 127 | if (ObjUtils.isNull(failureThreshold) || failureThreshold < 1){ 128 | failureThreshold = 1 129 | } 130 | if (ObjUtils.isNull(timeout) || timeout < 1){ 131 | timeout = 5 132 | } 133 | def periodMS = periodSec * 1000 134 | boolean isOnline = false 135 | for (int i = 0; i < failureThreshold; i++) { 136 | // steps.echo "健康检查-第${i}次" 137 | sleep periodMS 138 | def httpCode = "0" 139 | try { 140 | httpCode = steps.sh returnStdout: true, script: """curl -s -o /dev/null -w '%{http_code}' --connect-timeout ${timeout} ${url}""" 141 | } catch (Exception e) { 142 | } 143 | boolean httpListening = ObjUtils.isNotEmpty(httpCode) && httpCode.trim() == "200" 144 | if (httpListening){ 145 | steps.echo "url访问成功:${url},${timeout}" 146 | isOnline = true 147 | break 148 | } 149 | } 150 | return isOnline; 151 | } 152 | 153 | /** 154 | * CMD 155 | * @param localTCPPort 156 | * @return 157 | */ 158 | def healthCheckWithCMD(def command,def timeout,def periodSec,def failureThreshold) { 159 | AssertUtils.notNull(command,"command为空") 160 | steps.echo "检查http是能访问-参数${command},${timeout},间隔${periodSec},重试次数${failureThreshold}" 161 | if (ObjUtils.isNull(periodSec) || periodSec < 0){ 162 | periodSec = 0 163 | } 164 | if (ObjUtils.isNull(failureThreshold) || failureThreshold < 1){ 165 | failureThreshold = 1 166 | } 167 | if (ObjUtils.isNull(timeout) || timeout < 1){ 168 | timeout = 5 169 | } 170 | def periodMS = periodSec * 1000 171 | boolean isOnline = false 172 | for (int i = 0; i < failureThreshold; i++) { 173 | // steps.echo "健康检查-第${i}次" 174 | sleep periodMS 175 | def exitCode = 1 176 | try { 177 | steps.timeout(time: timeout, unit: 'SECONDS') { 178 | exitCode = steps.sh label: '执行command参数', returnStatus: true, script: command 179 | } 180 | } catch (Exception e) { 181 | exitCode = 1 182 | } 183 | boolean isSuccess = ObjUtils.isNotEmpty(exitCode) && exitCode == 0 184 | if (isSuccess){ 185 | steps.echo "CMD执行成功:${command},${timeout}" 186 | isOnline = true 187 | break 188 | } 189 | } 190 | return isOnline; 191 | } 192 | } -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/utils/StrUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.utils 2 | 3 | /** 4 | * @author daluobai@outlook.com 5 | * version 1.0.0 6 | * @title 字符串工具类 - 参考hutool StrUtil 7 | * @description https://github.com/daluobai-devops/jenkins-shared-library 8 | * @create 2025/12/13 9 | */ 10 | class StrUtils implements Serializable { 11 | 12 | /** 13 | * 字符串是否为空白 14 | * @param str 字符串 15 | * @return 是否为空白 16 | */ 17 | static boolean isBlank(CharSequence str) { 18 | int length 19 | if (str == null || (length = str.length()) == 0) { 20 | return true 21 | } 22 | for (int i = 0; i < length; i++) { 23 | // 判断是否包含非空白字符 24 | if (!Character.isWhitespace(str.charAt(i))) { 25 | return false 26 | } 27 | } 28 | return true 29 | } 30 | 31 | /** 32 | * 字符串是否为非空白 33 | * @param str 字符串 34 | * @return 是否为非空白 35 | */ 36 | static boolean isNotBlank(CharSequence str) { 37 | return !isBlank(str) 38 | } 39 | 40 | /** 41 | * 字符串是否为空 42 | * @param str 字符串 43 | * @return 是否为空 44 | */ 45 | static boolean isEmpty(CharSequence str) { 46 | return str == null || str.length() == 0 47 | } 48 | 49 | /** 50 | * 字符串是否为非空 51 | * @param str 字符串 52 | * @return 是否为非空 53 | */ 54 | static boolean isNotEmpty(CharSequence str) { 55 | return !isEmpty(str) 56 | } 57 | 58 | /** 59 | * 去除字符串首尾空白符 60 | * @param str 字符串 61 | * @return 去除空白后的字符串 62 | */ 63 | static String trim(CharSequence str) { 64 | return str == null ? null : str.toString().trim() 65 | } 66 | 67 | /** 68 | * 截取分隔字符串之前的字符串,不包括分隔字符串 69 | * @param str 被查找的字符串 70 | * @param separator 分隔字符串(不包括) 71 | * @param isLastSeparator 是否查找最后一个分隔字符串(多次出现分隔字符串时选取最后一个),true为选取最后一个 72 | * @return 切割后的字符串 73 | */ 74 | static String subBefore(CharSequence str, CharSequence separator, boolean isLastSeparator) { 75 | if (isEmpty(str) || separator == null) { 76 | return str == null ? null : str.toString() 77 | } 78 | 79 | final String strString = str.toString() 80 | final String separatorString = separator.toString() 81 | final int pos = isLastSeparator ? strString.lastIndexOf(separatorString) : strString.indexOf(separatorString) 82 | if (pos == -1) { 83 | return strString 84 | } 85 | return strString.substring(0, pos) 86 | } 87 | 88 | /** 89 | * 截取分隔字符串之后的字符串,不包括分隔字符串 90 | * @param str 被查找的字符串 91 | * @param separator 分隔字符串(不包括) 92 | * @param isLastSeparator 是否查找最后一个分隔字符串(多次出现分隔字符串时选取最后一个),true为选取最后一个 93 | * @return 切割后的字符串 94 | */ 95 | static String subAfter(CharSequence str, CharSequence separator, boolean isLastSeparator) { 96 | if (isEmpty(str) || isEmpty(separator)) { 97 | return str == null ? null : str.toString() 98 | } 99 | 100 | final String strString = str.toString() 101 | final String separatorString = separator.toString() 102 | final int pos = isLastSeparator ? strString.lastIndexOf(separatorString) : strString.indexOf(separatorString) 103 | if (pos == -1) { 104 | return strString 105 | } 106 | return strString.substring(pos + separatorString.length()) 107 | } 108 | 109 | /** 110 | * 格式化文本 111 | * @param template 文本模板,被替换的部分用 {} 表示 112 | * @param params 参数值 113 | * @return 格式化后的文本 114 | */ 115 | static String format(CharSequence template, Object... params) { 116 | if (null == template) { 117 | return null 118 | } 119 | if (params == null || params.length == 0 || isBlank(template)) { 120 | return template.toString() 121 | } 122 | 123 | final String templateString = template.toString() 124 | final StringBuilder sb = new StringBuilder(templateString.length() + 50) 125 | int handledPosition = 0 126 | int delimiterIndex 127 | for (int argIndex = 0; argIndex < params.length; argIndex++) { 128 | delimiterIndex = templateString.indexOf("{}", handledPosition) 129 | if (delimiterIndex == -1) { 130 | if (handledPosition == 0) { 131 | return templateString 132 | } 133 | // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 134 | sb.append(templateString.substring(handledPosition)) 135 | return sb.toString() 136 | } 137 | 138 | // 转义符 139 | if (delimiterIndex > 0 && templateString.charAt(delimiterIndex - 1) == '\\') { 140 | if (delimiterIndex > 1 && templateString.charAt(delimiterIndex - 2) == '\\') { 141 | // 双转义符,占位符依旧有效 142 | sb.append(templateString.substring(handledPosition, delimiterIndex - 1)) 143 | sb.append(params[argIndex]) 144 | handledPosition = delimiterIndex + 2 145 | } else { 146 | // 占位符被转义 147 | argIndex-- 148 | sb.append(templateString.substring(handledPosition, delimiterIndex - 1)) 149 | sb.append('{') 150 | handledPosition = delimiterIndex + 1 151 | } 152 | } else { 153 | // 正常占位符 154 | sb.append(templateString.substring(handledPosition, delimiterIndex)) 155 | sb.append(params[argIndex]) 156 | handledPosition = delimiterIndex + 2 157 | } 158 | } 159 | 160 | // 加入最后一个占位符后剩余的模板 161 | sb.append(templateString.substring(handledPosition)) 162 | return sb.toString() 163 | } 164 | 165 | /** 166 | * 比较两个字符串(大小写敏感) 167 | * @param str1 字符串1 168 | * @param str2 字符串2 169 | * @return 如果两个字符串相同,或者都是null,则返回true 170 | */ 171 | static boolean equals(CharSequence str1, CharSequence str2) { 172 | if (str1 == null) { 173 | return str2 == null 174 | } 175 | return str1.toString().equals(str2 == null ? null : str2.toString()) 176 | } 177 | 178 | /** 179 | * 比较两个字符串(大小写不敏感) 180 | * @param str1 字符串1 181 | * @param str2 字符串2 182 | * @return 如果两个字符串相同,或者都是null,则返回true 183 | */ 184 | static boolean equalsIgnoreCase(CharSequence str1, CharSequence str2) { 185 | if (str1 == null) { 186 | return str2 == null 187 | } 188 | return str1.toString().equalsIgnoreCase(str2 == null ? null : str2.toString()) 189 | } 190 | 191 | /** 192 | * 给定字符串是否以任何一个字符串开始 193 | * @param str 给定字符串 194 | * @param prefixes 需要检测的开始字符串 195 | * @return 给定字符串是否以任何一个字符串开始 196 | */ 197 | static boolean startWithAny(CharSequence str, CharSequence... prefixes) { 198 | if (isEmpty(str) || prefixes == null || prefixes.length == 0) { 199 | return false 200 | } 201 | 202 | for (CharSequence prefix : prefixes) { 203 | if (str.toString().startsWith(prefix.toString())) { 204 | return true 205 | } 206 | } 207 | return false 208 | } 209 | 210 | /** 211 | * 如果给定字符串为空,则返回默认字符串 212 | * @param str 字符串 213 | * @param defaultStr 默认字符串 214 | * @return 字符串本身或指定的默认字符串 215 | */ 216 | static String emptyToDefault(CharSequence str, String defaultStr) { 217 | return isEmpty(str) ? defaultStr : str.toString() 218 | } 219 | 220 | /** 221 | * 如果给定字符串为空白,则返回默认字符串 222 | * @param str 字符串 223 | * @param defaultStr 默认字符串 224 | * @return 字符串本身或指定的默认字符串 225 | */ 226 | static String blankToDefault(CharSequence str, String defaultStr) { 227 | return isBlank(str) ? defaultStr : str.toString() 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/utils/ObjUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.utils 2 | 3 | /** 4 | * @author daluobai@outlook.com 5 | * version 1.0.0 6 | * @title 对象工具类 - 参考hutool ObjectUtil 7 | * @description https://github.com/daluobai-devops/jenkins-shared-library 8 | * @create 2025/12/13 9 | */ 10 | class ObjUtils implements Serializable { 11 | 12 | /** 13 | * 判断对象是否为null 14 | * @param obj 对象 15 | * @return 是否为null 16 | */ 17 | static boolean isNull(Object obj) { 18 | return obj == null 19 | } 20 | 21 | /** 22 | * 判断对象是否不为null 23 | * @param obj 对象 24 | * @return 是否不为null 25 | */ 26 | static boolean isNotNull(Object obj) { 27 | return obj != null 28 | } 29 | 30 | /** 31 | * 判断对象是否为空 32 | * 支持:String、Collection、Map、Array 33 | * @param obj 对象 34 | * @return 是否为空 35 | */ 36 | static boolean isEmpty(Object obj) { 37 | if (obj == null) { 38 | return true 39 | } 40 | 41 | if (obj instanceof CharSequence) { 42 | return ((CharSequence) obj).length() == 0 43 | } 44 | 45 | if (obj instanceof Collection) { 46 | return ((Collection) obj).isEmpty() 47 | } 48 | 49 | if (obj instanceof Map) { 50 | return ((Map) obj).isEmpty() 51 | } 52 | 53 | if (obj.getClass().isArray()) { 54 | return ((Object[]) obj).length == 0 55 | } 56 | 57 | return false 58 | } 59 | 60 | /** 61 | * 判断对象是否不为空 62 | * @param obj 对象 63 | * @return 是否不为空 64 | */ 65 | static boolean isNotEmpty(Object obj) { 66 | return !isEmpty(obj) 67 | } 68 | 69 | /** 70 | * 如果对象为null,返回默认值 71 | * @param obj 对象 72 | * @param defaultValue 默认值 73 | * @return 对象本身或默认值 74 | */ 75 | static T defaultIfNull(T obj, T defaultValue) { 76 | return obj == null ? defaultValue : obj 77 | } 78 | 79 | /** 80 | * 如果对象为空,返回默认值 81 | * @param obj 对象 82 | * @param defaultValue 默认值 83 | * @return 对象本身或默认值 84 | */ 85 | static T defaultIfEmpty(T obj, T defaultValue) { 86 | return isEmpty(obj) ? defaultValue : obj 87 | } 88 | 89 | /** 90 | * 判断对象是否相等 91 | * @param obj1 对象1 92 | * @param obj2 对象2 93 | * @return 是否相等 94 | */ 95 | static boolean equals(Object obj1, Object obj2) { 96 | if (obj1 == obj2) { 97 | return true 98 | } 99 | if (obj1 == null || obj2 == null) { 100 | return false 101 | } 102 | return obj1.equals(obj2) 103 | } 104 | 105 | /** 106 | * 判断对象是否不相等 107 | * @param obj1 对象1 108 | * @param obj2 对象2 109 | * @return 是否不相等 110 | */ 111 | static boolean notEquals(Object obj1, Object obj2) { 112 | return !equals(obj1, obj2) 113 | } 114 | 115 | /** 116 | * 克隆对象 117 | * @param obj 对象 118 | * @return 克隆后的对象 119 | */ 120 | static T clone(T obj) { 121 | if (obj == null) { 122 | return null 123 | } 124 | 125 | if (obj instanceof Cloneable) { 126 | return (T) obj.clone() 127 | } 128 | 129 | throw new IllegalArgumentException("对象不支持克隆") 130 | } 131 | 132 | /** 133 | * 获取对象的字符串表示 134 | * @param obj 对象 135 | * @return 字符串表示 136 | */ 137 | static String toString(Object obj) { 138 | return obj == null ? null : obj.toString() 139 | } 140 | 141 | /** 142 | * 获取对象的字符串表示,如果为null则返回默认值 143 | * @param obj 对象 144 | * @param defaultStr 默认值 145 | * @return 字符串表示 146 | */ 147 | static String toString(Object obj, String defaultStr) { 148 | return obj == null ? defaultStr : obj.toString() 149 | } 150 | 151 | /** 152 | * 判断对象是否为基本类型 153 | * @param obj 对象 154 | * @return 是否为基本类型 155 | */ 156 | static boolean isBasicType(Object obj) { 157 | if (obj == null) { 158 | return false 159 | } 160 | 161 | Class clazz = obj.getClass() 162 | return clazz.isPrimitive() || 163 | obj instanceof String || 164 | obj instanceof Number || 165 | obj instanceof Boolean || 166 | obj instanceof Character 167 | } 168 | 169 | /** 170 | * 将对象转为指定类型 171 | * @param obj 对象 172 | * @param targetClass 目标类型 173 | * @return 转换后的对象 174 | */ 175 | static T convert(Object obj, Class targetClass) { 176 | if (obj == null) { 177 | return null 178 | } 179 | 180 | if (targetClass.isInstance(obj)) { 181 | return (T) obj 182 | } 183 | 184 | // String转换 185 | if (targetClass == String.class) { 186 | return (T) obj.toString() 187 | } 188 | 189 | // 数字转换 190 | if (obj instanceof Number) { 191 | Number number = (Number) obj 192 | if (targetClass == Integer.class || targetClass == int.class) { 193 | return (T) Integer.valueOf(number.intValue()) 194 | } 195 | if (targetClass == Long.class || targetClass == long.class) { 196 | return (T) Long.valueOf(number.longValue()) 197 | } 198 | if (targetClass == Double.class || targetClass == double.class) { 199 | return (T) Double.valueOf(number.doubleValue()) 200 | } 201 | if (targetClass == Float.class || targetClass == float.class) { 202 | return (T) Float.valueOf(number.floatValue()) 203 | } 204 | if (targetClass == Short.class || targetClass == short.class) { 205 | return (T) Short.valueOf(number.shortValue()) 206 | } 207 | if (targetClass == Byte.class || targetClass == byte.class) { 208 | return (T) Byte.valueOf(number.byteValue()) 209 | } 210 | } 211 | 212 | // 字符串转数字 213 | if (obj instanceof String) { 214 | String str = (String) obj 215 | if (targetClass == Integer.class || targetClass == int.class) { 216 | return (T) Integer.valueOf(str) 217 | } 218 | if (targetClass == Long.class || targetClass == long.class) { 219 | return (T) Long.valueOf(str) 220 | } 221 | if (targetClass == Double.class || targetClass == double.class) { 222 | return (T) Double.valueOf(str) 223 | } 224 | if (targetClass == Float.class || targetClass == float.class) { 225 | return (T) Float.valueOf(str) 226 | } 227 | if (targetClass == Boolean.class || targetClass == boolean.class) { 228 | return (T) Boolean.valueOf(str) 229 | } 230 | } 231 | 232 | throw new IllegalArgumentException("不支持的类型转换: " + obj.getClass() + " -> " + targetClass) 233 | } 234 | 235 | /** 236 | * 判断集合中是否包含指定元素 237 | * @param collection 集合 238 | * @param element 元素 239 | * @return 是否包含 240 | */ 241 | static boolean contains(Collection collection, Object element) { 242 | if (collection == null || collection.isEmpty()) { 243 | return false 244 | } 245 | return collection.contains(element) 246 | } 247 | 248 | /** 249 | * 判断数组中是否包含指定元素 250 | * @param array 数组 251 | * @param element 元素 252 | * @return 是否包含 253 | */ 254 | static boolean contains(Object[] array, Object element) { 255 | if (array == null || array.length == 0) { 256 | return false 257 | } 258 | 259 | for (Object item : array) { 260 | if (equals(item, element)) { 261 | return true 262 | } 263 | } 264 | 265 | return false 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /vars/deployJavaWeb.groovy: -------------------------------------------------------------------------------- 1 | import com.daluobai.jenkinslib.utils.StrUtils 2 | import com.daluobai.jenkinslib.utils.AssertUtils 3 | import com.daluobai.jenkinslib.utils.ObjUtils 4 | import com.daluobai.jenkinslib.constant.EBuildStatusType 5 | import com.daluobai.jenkinslib.constant.EFileReadType 6 | import com.daluobai.jenkinslib.utils.ConfigUtils 7 | import com.daluobai.jenkinslib.utils.MapUtils 8 | import com.daluobai.jenkinslib.steps.* 9 | import com.daluobai.jenkinslib.utils.MessageUtils 10 | import com.daluobai.jenkinslib.utils.ConfigMergeUtils 11 | import groovy.transform.Field 12 | 13 | @Field Map globalParameterMap = [:] 14 | 15 | /** 16 | * @author daluobai@outlook.com 17 | * version 1.0.0 18 | * @title 19 | * @description https://github.com/daluobai-devops/jenkins-shared-library 20 | * @create 2023/4/25 12:10 21 | */ 22 | def call(Map customConfig) { 23 | 24 | /*******************初始化全局对象 开始*****************/ 25 | def stepsBuildMaven = new StepsBuildMaven(this) 26 | def stepsJenkins = new StepsJenkins(this) 27 | def stepsJavaWeb = new StepsJavaWeb(this) 28 | def configUtils = new ConfigUtils(this) 29 | def messageUtils = new MessageUtils(this) 30 | def stepsTomcat = new StepsTomcat(this) 31 | def stepsDeploy = new StepsDeploy(this) 32 | /*******************初始化全局对象 结束*****************/ 33 | //用来运行构建的节点 34 | def nodeBuildNodeList = stepsJenkins.getNodeByLabel("buildNode") 35 | echo "获取到节点:${nodeBuildNodeList}" 36 | if (ObjUtils.isEmpty(nodeBuildNodeList)) { 37 | error '没有可用的构建节点' 38 | } 39 | 40 | /***初始化参数 开始**/ 41 | //错误信息 42 | def errMessage = "" 43 | EBuildStatusType eBuildStatusType = EBuildStatusType.FAILED 44 | //DEPLOY_PIPELINE顺序定义 45 | def deployPipelineIndex = ["stepsBuild", "stepsStorage", "stepsDeploy"] 46 | //如果没传项目名称,则使用jenkins项目名称 47 | if (StrUtils.isBlank(customConfig.SHARE_PARAM.appName)) { 48 | customConfig.SHARE_PARAM.appName = currentBuild.projectName 49 | } 50 | def SHARE_PARAM = customConfig.SHARE_PARAM 51 | /***初始化参数 结束**/ 52 | //默认在同一个构建节点运行,如果需要在其他节点运行则单独写在node块中 53 | node(nodeBuildNodeList[0]) { 54 | try { 55 | //获取并合并配置 56 | def fullConfig = mergeConfig(customConfig) 57 | echo "fullConfig: ${fullConfig.toString()}" 58 | //设置共享参数。 59 | globalParameterMap = fullConfig 60 | 61 | messageUtils.sendMessage(false,customConfig.SHARE_PARAM.message, "发布开始:${customConfig.SHARE_PARAM.appName}", "发布开始: ${currentBuild.fullDisplayName}") 62 | 63 | //执行流程 64 | deployPipelineIndex.each { 65 | stage("${it}") { 66 | def pipelineConfigItemMap = fullConfig.DEPLOY_PIPELINE[it] 67 | if (pipelineConfigItemMap["enable"] != null && pipelineConfigItemMap["enable"] == false) { 68 | echo "跳过流程: ${it}" 69 | return 70 | } 71 | echo "开始执行流程: ${it}" 72 | if (it == "stepsBuild") { 73 | //设置环境变量 74 | def stepsBuildEnvList = [] 75 | if (fullConfig.DEPLOY_PIPELINE.stepsBuild.stepsBuildEnv){ 76 | stepsBuildEnvList = fullConfig.DEPLOY_PIPELINE.stepsBuild.stepsBuildEnv.collect { k, v -> "${k}=${v}" } 77 | } 78 | withEnv(stepsBuildEnvList) { 79 | stepsBuildMaven.build(fullConfig) 80 | } 81 | } else if (it == "stepsStorage") { 82 | if (ObjUtils.isEmpty(pipelineConfigItemMap)) { 83 | error "stepsStorage配置为空" 84 | } 85 | stepsJenkins.stash(pipelineConfigItemMap) 86 | } else if (it == "stepsDeploy") { 87 | messageUtils.sendMessage(false,customConfig.SHARE_PARAM.message, "准备重启:${customConfig.SHARE_PARAM.appName}", "准备重启: ${currentBuild.fullDisplayName}") 88 | stepsDeploy.deploy(pipelineConfigItemMap) 89 | } 90 | } 91 | echo "结束执行流程: ${it}" 92 | } 93 | eBuildStatusType = EBuildStatusType.SUCCESS 94 | } catch (Exception e) { 95 | if (e instanceof org.jenkinsci.plugins.workflow.steps.FlowInterruptedException) { 96 | eBuildStatusType = EBuildStatusType.ABORTED 97 | } else { 98 | eBuildStatusType = EBuildStatusType.FAILED 99 | errMessage = e.getMessage() 100 | } 101 | throw e 102 | } finally { 103 | if (ObjUtils.isNotEmpty(customConfig.SHARE_PARAM.message)) { 104 | def messageTitle = "" 105 | def messageContent = "" 106 | if (eBuildStatusType == EBuildStatusType.SUCCESS) { 107 | messageTitle = "成功:${customConfig.SHARE_PARAM.appName}" 108 | messageContent = "发布成功: ${currentBuild.fullDisplayName}" 109 | } else if (eBuildStatusType == EBuildStatusType.FAILED) { 110 | messageTitle = "失败:${customConfig.SHARE_PARAM.appName}" 111 | messageContent = "发布失败: ${currentBuild.fullDisplayName},异常信息: ${errMessage},构建日志:(${BUILD_URL}console)" 112 | } else if (eBuildStatusType == EBuildStatusType.ABORTED) { 113 | //发布终止 114 | } 115 | if (StrUtils.isNotBlank(messageTitle) && StrUtils.isNotBlank(messageContent)) { 116 | messageUtils.sendMessage(true,customConfig.SHARE_PARAM.message, messageTitle, messageContent) 117 | } 118 | } 119 | deleteDir() 120 | } 121 | } 122 | } 123 | 124 | //获取默认配置路径 125 | def defaultConfigPath(EFileReadType eConfigType) { 126 | AssertUtils.notNull(eConfigType, "配置类型为空") 127 | def configPath = null 128 | if (eConfigType == EFileReadType.HOST_PATH) { 129 | configPath = "/usr/local/workspace/config/jenkins-pipeline/jenkins-pipeline-config/config.json" 130 | } else if (eConfigType == EFileReadType.RESOURCES) { 131 | configPath = "config/config.json" 132 | } else { 133 | throw new Exception("暂无默认配置类型") 134 | } 135 | return configPath 136 | } 137 | 138 | //合并配置customConfig >> extendConfig >> defaultConfig = fullConfig 139 | def mergeConfig(Map customConfig) { 140 | 141 | def fullConfig = [:] 142 | def extendConfig = [:] 143 | def defaultConfig = [:] 144 | //读取默认配置文件 145 | defaultConfig = new ConfigUtils(this).readConfig(EFileReadType.RESOURCES, defaultConfigPath(EFileReadType.RESOURCES)) 146 | echo "customConfig: ${customConfig.toString()}" 147 | echo "defaultConfig: ${defaultConfig.toString()}" 148 | //读取继承配置文件 149 | if (ObjUtils.isNotEmpty(customConfig.CONFIG_EXTEND) && ObjUtils.isNotEmpty(EFileReadType.get(customConfig.CONFIG_EXTEND.configFullPath))) { 150 | extendConfig = new ConfigUtils(this).readConfigFromFullPath(customConfig.CONFIG_EXTEND.configFullPath) 151 | echo "extendConfig: ${extendConfig.toString()}" 152 | } 153 | //合并自定义配置 154 | fullConfig = MapUtils.merge([defaultConfig, extendConfig, customConfig]) 155 | 156 | //根据自定义构建参数,修改配置 157 | fullConfig = ConfigMergeUtils.mergeParams(fullConfig, params) 158 | echo "fullConfig merged: ${fullConfig}" 159 | 160 | compatibleConfig(fullConfig) 161 | echo "fullConfig compatible: ${fullConfig}" 162 | return MapUtils.deepCopy(fullConfig) 163 | } 164 | 165 | //兼容旧的配置 166 | def compatibleConfig(Map customConfig) { 167 | if (customConfig.DEPLOY_PIPELINE.stepsBuildMaven){ 168 | customConfig.DEPLOY_PIPELINE.stepsBuild = ["stepsBuildMaven":[],"enable":false] 169 | customConfig.DEPLOY_PIPELINE.stepsBuild.stepsBuildMaven = customConfig.DEPLOY_PIPELINE.stepsBuildMaven 170 | customConfig.DEPLOY_PIPELINE.stepsBuild.enable = true 171 | } 172 | return customConfig 173 | } 174 | 175 | 176 | -------------------------------------------------------------------------------- /configdemo/deployJavaWeb_Old.groovy: -------------------------------------------------------------------------------- 1 | def customConfig = [ 2 | //公共参数 3 | "SHARE_PARAM" : [ 4 | //app 名称,如果没填则使用jenkins job名称。可不填 5 | "appName": "test", 6 | //消息通知,可不填 7 | "message": [ 8 | //企业微信通知 可不填 9 | "wecom" : [ 10 | //企业微信机器人token 必填 11 | "key": "", 12 | //是否发送完整的消息,如果为true只发送部署成功的消息。默认false 13 | "fullMessage": false 14 | ], 15 | //飞书通知 可不填 16 | "feishu": [ 17 | //飞书机器人token 必填 18 | "token" : "", 19 | //是否发送完整的消息,如果为true只发送部署成功的消息。默认false 20 | "fullMessage": false 21 | ] 22 | ] 23 | ], 24 | //发布流程 25 | "DEPLOY_PIPELINE": [ 26 | //构建 27 | "stepsBuildMaven": [ 28 | //是否激活,默认true 29 | "enable" : true, 30 | //app git url 必填. 31 | "gitUrl" : "git@gitee.com:renrenio/renren-security.git", 32 | //git 分支 33 | "gitBranch" : "master", 34 | //子模块目录,如果要构建子模块填入子模块目录。 可不填 35 | "subModule" : "renren-api/", 36 | //是否跳过测试 可不填 37 | "skipTest" : true, 38 | //生命周期 必填 39 | "lifecycle" : "clean package", 40 | //settings.xml文件路径,支持URL,HOST_PATH,RESOURCES 可不填 41 | "settingsFullPath": "RESOURCES:config/settings.xml", 42 | //用来打包的镜像,可不填.默认3-jdk8,选项(3-jdk7,3-jdk8,3-jdk17,3-jdk21) 43 | "dockerBuildImage": "registry.cn-hangzhou.aliyuncs.com/wuzhaozhongguo/build-maven:3-jdk8", 44 | //激活的profile,maven -P参数 可不填 45 | "activeProfile" : "dev" 46 | ], 47 | //存储 48 | "stepsStorage" : [ 49 | //是否激活,默认true 50 | "enable" : true, 51 | //构建产物类型 JAR,WAR 必填 52 | "archiveType" : "JAR", 53 | //存储构建产物,构建成功后可以在页面下载构建产物。默认false 54 | "archiveArtifacts": false, 55 | //存储类型 jenkinsStash,dockerRegistry 必填 56 | "jenkinsStash" : [ 57 | //是否激活,默认false 58 | "enable": true, 59 | ], 60 | "dockerRegistry" : [ 61 | //是否激活,默认false 62 | "enable" : false, 63 | "imagePrefix" : "registry.cn-hangzhou.aliyuncs.com/wuzhaozhongguo-app", 64 | //如果不填则使用appName 65 | "imageName" : "prod-xxx-api", 66 | //如果不填则使用日期作为版本号 67 | "imageVersion": "latest", 68 | "dockerfile" : [ 69 | "url" : "git@github.com:daluobai-devops/docker-library.git", 70 | "gitBranch": "main", 71 | "path" : "package-javaweb/openjdk8" 72 | ], 73 | "buildArgs" : ["runOptions": "-Xms128M -Xmx128M", "runArgs": "--spring.profiles.active=dev"] 74 | ], 75 | ], 76 | //发布 77 | "stepsDeploy" : [ 78 | //是否激活,默认true 79 | "enable" : true, 80 | //服务发布服务label 必填 81 | "labels" : ["NODE-DEMO"], 82 | //发布 83 | "stepsJavaWebDeployToService": [ 84 | //是否激活,默认true 85 | "enable" : true, 86 | //java路径 可不填 87 | "javaPath" : "/usr/local/jdk/jdk8/bin/java", 88 | //服务发布路径 必填 89 | "pathRoot" : "/apps/application/", 90 | //JVM参数 [-options] 示例(-Dfile.encoding=UTF-8 -Xms128M -Xmx128M -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005) 91 | "runOptions": "-Xms128M -Xmx128M", 92 | //启动参数 [args...] 示例(-–spring.profiles.active=dev) 93 | "runArgs" : "--spring.profiles.active=dev", 94 | //服务管理方式,支持systemctl,shell,默认systemctl,如果系统没有systemctl命令则使用shell方式 95 | "manageBy" : "systemctl", 96 | ], 97 | //发布到tomcat 98 | "stepsTomcatDeploy" : [ 99 | //是否激活,默认true 100 | "enable" : false, 101 | //工作目录 必选。备份用 102 | "tomcatHome": "/usr/local/tomcat", 103 | //包发布路径 必填 104 | "deployPath": "/usr/local/tomcat/webapps/", 105 | //重启脚本,可不填 106 | "command" : "cd /usr/local/tomcat/bin/ && ./shutdown.sh && sleep 1000 && ./startup.sh" 107 | ], 108 | 109 | //就绪探针 可不填,检查服务是否启动成功,如果启动成功则认为服务发布成功,如果不填则不检查.探针类型,支持http,tcp,cmd. 110 | "readinessProbe" : [ 111 | //检查端口是否监听,如果监听则认为发布成功,如果不填则不检查 可不填 112 | tcp : [ 113 | //是否激活,默认true 114 | "enable": true, 115 | //探针端口 116 | "port" : 8080 117 | ], 118 | //访问http地址,http状态码返回200则认为发布成功,如果不填则不检查 可不填 119 | http : [ 120 | //是否激活,默认true 121 | "enable" : false, 122 | //探针路径, 必填 123 | "path" : "/actuator/health", 124 | //探针端口, 必填 125 | "port" : 8080, 126 | //探针超时时间,单位秒,默认5秒 可不填 127 | "timeout": 5 128 | ], 129 | //执行命令,以退出状态码判断是否成功 可不填 130 | cmd : [ 131 | //是否激活,默认true 132 | "enable" : false, 133 | //探针命令,如果type为cmd则必填 必填 134 | "command": "curl -s 'http://localhost:8080/actuator/health' | grep -q 'UP' && echo 0 || echo 1", 135 | //探针超时时间,单位秒,默认5秒 可不填 136 | "timeout": 5 137 | ], 138 | //探针间隔时间,单位秒,默认5秒 可不填 139 | "period" : 5, 140 | //探针失败次数,如果失败次数达到该值则认为发布失败,默认3次 可不填 141 | "failureThreshold": 20 142 | ], 143 | //所有部署流程执行完成后运行的命令,centos(firewall-cmd --zone=public --add-port=8080/tcp --permanent && firewall-cmd --reload),ubuntu(ufw allow 8080/tcp && ufw reload) 144 | "afterRunCMD" : "" 145 | ] 146 | ], 147 | //默认配置 148 | "DEFAULT_CONFIG" : [ 149 | "docker": [ 150 | "registry": [ 151 | "domain": "docker.io" 152 | ] 153 | ] 154 | ], 155 | // //继承配置 156 | "CONFIG_EXTEND" : [ 157 | //配置文件完整路径configType:path,支持URL,HOST_PATH,RESOURCES,默认RESOURCES. 必填. 158 | "configFullPath": "RESOURCES:config/config.json", 159 | ] 160 | ] 161 | @Library('jenkins-shared-library') _ 162 | deployJavaWeb(customConfig) -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/utils/IoUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.utils 2 | 3 | import java.nio.charset.Charset 4 | 5 | /** 6 | * @author daluobai@outlook.com 7 | * version 1.0.0 8 | * @title 文件工具类 - 参考hutool FileUtil 9 | * @description https://github.com/daluobai-devops/jenkins-shared-library 10 | * @create 2025/12/13 11 | */ 12 | class IoUtils implements Serializable { 13 | 14 | /** 15 | * 判断是否为文件 16 | * @param file 文件 17 | * @return 如果为文件,则返回true 18 | */ 19 | static boolean isFile(File file) { 20 | return file != null && file.exists() && file.isFile() 21 | } 22 | 23 | /** 24 | * 判断是否为目录 25 | * @param file 文件 26 | * @return 如果为目录,则返回true 27 | */ 28 | static boolean isDirectory(File file) { 29 | return file != null && file.exists() && file.isDirectory() 30 | } 31 | 32 | /** 33 | * 判断文件是否存在 34 | * @param file 文件 35 | * @return 如果存在,则返回true 36 | */ 37 | static boolean exists(File file) { 38 | return file != null && file.exists() 39 | } 40 | 41 | /** 42 | * 读取文件内容为字符串 43 | * @param file 文件 44 | * @param charset 字符集 45 | * @return 文件内容 46 | */ 47 | static String readString(File file, Charset charset = Charset.forName("UTF-8")) { 48 | if (file == null || !file.exists()) { 49 | return null 50 | } 51 | 52 | try { 53 | return file.getText(charset.name()) 54 | } catch (IOException e) { 55 | throw new RuntimeException("读取文件失败: " + file.getAbsolutePath(), e) 56 | } 57 | } 58 | 59 | /** 60 | * 读取文件内容为字符串 61 | * @param filePath 文件路径 62 | * @param charset 字符集 63 | * @return 文件内容 64 | */ 65 | static String readString(String filePath, Charset charset = Charset.forName("UTF-8")) { 66 | if (StrUtils.isBlank(filePath)) { 67 | return null 68 | } 69 | return readString(new File(filePath), charset) 70 | } 71 | 72 | /** 73 | * 写入字符串到文件 74 | * @param content 内容 75 | * @param file 文件 76 | * @param charset 字符集 77 | */ 78 | static void writeString(String content, File file, Charset charset = Charset.forName("UTF-8")) { 79 | if (file == null) { 80 | throw new IllegalArgumentException("文件不能为null") 81 | } 82 | 83 | try { 84 | // 确保父目录存在 85 | File parent = file.getParentFile() 86 | if (parent != null && !parent.exists()) { 87 | parent.mkdirs() 88 | } 89 | 90 | file.write(content, charset.name()) 91 | } catch (IOException e) { 92 | throw new RuntimeException("写入文件失败: " + file.getAbsolutePath(), e) 93 | } 94 | } 95 | 96 | /** 97 | * 写入字符串到文件 98 | * @param content 内容 99 | * @param filePath 文件路径 100 | * @param charset 字符集 101 | */ 102 | static void writeString(String content, String filePath, Charset charset = Charset.forName("UTF-8")) { 103 | if (StrUtils.isBlank(filePath)) { 104 | throw new IllegalArgumentException("文件路径不能为空") 105 | } 106 | writeString(content, new File(filePath), charset) 107 | } 108 | 109 | /** 110 | * 追加字符串到文件 111 | * @param content 内容 112 | * @param file 文件 113 | * @param charset 字符集 114 | */ 115 | static void appendString(String content, File file, Charset charset = Charset.forName("UTF-8")) { 116 | if (file == null) { 117 | throw new IllegalArgumentException("文件不能为null") 118 | } 119 | 120 | try { 121 | // 确保父目录存在 122 | File parent = file.getParentFile() 123 | if (parent != null && !parent.exists()) { 124 | parent.mkdirs() 125 | } 126 | 127 | file.append(content, charset.name()) 128 | } catch (IOException e) { 129 | throw new RuntimeException("追加文件失败: " + file.getAbsolutePath(), e) 130 | } 131 | } 132 | 133 | /** 134 | * 读取文件的所有行 135 | * @param file 文件 136 | * @param charset 字符集 137 | * @return 行列表 138 | */ 139 | static List readLines(File file, Charset charset = Charset.forName("UTF-8")) { 140 | if (file == null || !file.exists()) { 141 | return [] 142 | } 143 | 144 | try { 145 | return file.readLines(charset.name()) 146 | } catch (IOException e) { 147 | throw new RuntimeException("读取文件失败: " + file.getAbsolutePath(), e) 148 | } 149 | } 150 | 151 | /** 152 | * 删除文件或目录 153 | * @param file 文件或目录 154 | * @return 是否删除成功 155 | */ 156 | static boolean delete(File file) { 157 | if (file == null || !file.exists()) { 158 | return true 159 | } 160 | 161 | if (file.isDirectory()) { 162 | // 递归删除目录 163 | File[] files = file.listFiles() 164 | if (files != null) { 165 | for (File f : files) { 166 | delete(f) 167 | } 168 | } 169 | } 170 | 171 | return file.delete() 172 | } 173 | 174 | /** 175 | * 创建文件及其父目录 176 | * @param file 文件 177 | * @return 是否创建成功 178 | */ 179 | static boolean createNewFile(File file) { 180 | if (file == null) { 181 | return false 182 | } 183 | 184 | if (file.exists()) { 185 | return true 186 | } 187 | 188 | try { 189 | // 确保父目录存在 190 | File parent = file.getParentFile() 191 | if (parent != null && !parent.exists()) { 192 | parent.mkdirs() 193 | } 194 | 195 | return file.createNewFile() 196 | } catch (IOException e) { 197 | throw new RuntimeException("创建文件失败: " + file.getAbsolutePath(), e) 198 | } 199 | } 200 | 201 | /** 202 | * 创建目录 203 | * @param dir 目录 204 | * @return 是否创建成功 205 | */ 206 | static boolean mkdir(File dir) { 207 | if (dir == null) { 208 | return false 209 | } 210 | 211 | if (dir.exists()) { 212 | return dir.isDirectory() 213 | } 214 | 215 | return dir.mkdirs() 216 | } 217 | 218 | /** 219 | * 复制文件 220 | * @param src 源文件 221 | * @param dest 目标文件 222 | * @param isOverride 是否覆盖已存在的文件 223 | */ 224 | static void copy(File src, File dest, boolean isOverride = true) { 225 | if (src == null || !src.exists()) { 226 | throw new IllegalArgumentException("源文件不存在") 227 | } 228 | 229 | if (dest == null) { 230 | throw new IllegalArgumentException("目标文件不能为null") 231 | } 232 | 233 | if (dest.exists() && !isOverride) { 234 | throw new IllegalArgumentException("目标文件已存在") 235 | } 236 | 237 | try { 238 | // 确保目标目录存在 239 | File parent = dest.getParentFile() 240 | if (parent != null && !parent.exists()) { 241 | parent.mkdirs() 242 | } 243 | 244 | dest.withOutputStream { os -> 245 | src.withInputStream { is -> 246 | os << is 247 | } 248 | } 249 | } catch (IOException e) { 250 | throw new RuntimeException("复制文件失败", e) 251 | } 252 | } 253 | 254 | /** 255 | * 获取文件扩展名 256 | * @param file 文件 257 | * @return 扩展名(不包含点) 258 | */ 259 | static String getExtension(File file) { 260 | if (file == null) { 261 | return null 262 | } 263 | return getExtension(file.getName()) 264 | } 265 | 266 | /** 267 | * 获取文件扩展名 268 | * @param fileName 文件名 269 | * @return 扩展名(不包含点) 270 | */ 271 | static String getExtension(String fileName) { 272 | if (StrUtils.isBlank(fileName)) { 273 | return null 274 | } 275 | 276 | int index = fileName.lastIndexOf('.') 277 | if (index == -1) { 278 | return "" 279 | } 280 | 281 | return fileName.substring(index + 1) 282 | } 283 | 284 | /** 285 | * 获取文件名(不包含扩展名) 286 | * @param file 文件 287 | * @return 文件名 288 | */ 289 | static String getNameWithoutExtension(File file) { 290 | if (file == null) { 291 | return null 292 | } 293 | return getNameWithoutExtension(file.getName()) 294 | } 295 | 296 | /** 297 | * 获取文件名(不包含扩展名) 298 | * @param fileName 文件名 299 | * @return 文件名 300 | */ 301 | static String getNameWithoutExtension(String fileName) { 302 | if (StrUtils.isBlank(fileName)) { 303 | return null 304 | } 305 | 306 | int index = fileName.lastIndexOf('.') 307 | if (index == -1) { 308 | return fileName 309 | } 310 | 311 | return fileName.substring(0, index) 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Stop when "xargs" is not available. 209 | if ! command -v xargs >/dev/null 2>&1 210 | then 211 | die "xargs is not available" 212 | fi 213 | 214 | # Use "xargs" to parse quoted args. 215 | # 216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 217 | # 218 | # In Bash we could simply go: 219 | # 220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 221 | # set -- "${ARGS[@]}" "$@" 222 | # 223 | # but POSIX shell has neither arrays nor command substitution, so instead we 224 | # post-process each arg (as a line of input to sed) to backslash-escape any 225 | # character that might be a shell metacharacter, then use eval to reverse 226 | # that process (while maintaining the separation between arguments), and wrap 227 | # the whole thing up as a single "set" statement. 228 | # 229 | # This will of course break if any of these variables contains a newline or 230 | # an unmatched quote. 231 | # 232 | 233 | eval "set -- $( 234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 235 | xargs -n1 | 236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 237 | tr '\n' ' ' 238 | )" '"$@"' 239 | 240 | exec "$JAVACMD" "$@" 241 | -------------------------------------------------------------------------------- /configdemo/deployJavaWeb.groovy: -------------------------------------------------------------------------------- 1 | def customConfig = [ 2 | //公共参数 3 | "SHARE_PARAM" : [ 4 | //app 名称,如果没填则使用jenkins job名称。可不填 5 | "appName": "test", 6 | //DEPLOY(发布),SERVICE(服务管理,RESTART,STOP,START),TEARDOWN(销毁),默认DEPLOY 7 | "model" : [ 8 | "type": "DEPLOY" 9 | ], 10 | //消息通知,可不填 11 | "message": [ 12 | //企业微信通知 可不填 13 | "wecom" : [ 14 | //企业微信机器人token 必填 15 | "key" : "", 16 | //是否发送完整的消息,如果为true只发送部署成功的消息。默认false 17 | "fullMessage": false 18 | ], 19 | //飞书通知 可不填 20 | "feishu": [ 21 | //飞书机器人token 必填 22 | "token" : "", 23 | //是否发送完整的消息,如果为true只发送部署成功的消息。默认false 24 | "fullMessage": false 25 | ] 26 | ] 27 | ], 28 | //发布流程 29 | "DEPLOY_PIPELINE": [ 30 | //构建 31 | "stepsBuild" : [ 32 | //是否激活,默认true 33 | "enable" : true, 34 | //构建环境变量(只在构建中生效),SPRING_PROFILES_ACTIVE:dev 35 | "stepsBuildEnv" : [ 36 | "SPRING_PROFILES_ACTIVE": "dev" 37 | ], 38 | //maven 39 | "stepsBuildMaven": [ 40 | //是否激活,默认true 41 | "enable" : true, 42 | //app git url 必填. 43 | "gitUrl" : "git@gitee.com:renrenio/renren-security.git", 44 | //git 分支 45 | "gitBranch" : "master", 46 | //子模块目录,如果要构建子模块填入子模块目录。 可不填 47 | "subModule" : "renren-api/", 48 | //是否跳过测试 可不填 49 | "skipTest" : true, 50 | //生命周期 必填 51 | "lifecycle" : "clean package", 52 | //settings.xml文件路径,支持URL,HOST_PATH,RESOURCES 可不填 53 | "settingsFullPath": "RESOURCES:config/settings.xml", 54 | //用来打包的镜像,可不填.默认3-jdk8,选项(3-jdk7,3-jdk8,3-jdk17,3-jdk21) 55 | "dockerBuildImage": "registry.cn-hangzhou.aliyuncs.com/wuzhaozhongguo/build-maven:3-jdk8", 56 | //激活的profile,maven -P参数.ps:dev 可不填 57 | "activeProfile" : "" 58 | ] 59 | ], 60 | //存储 61 | "stepsStorage": [ 62 | //是否激活,默认true 63 | "enable" : true, 64 | //构建产物类型 JAR,WAR 必填 65 | "archiveType" : "JAR", 66 | //存储构建产物,构建成功后可以在页面下载构建产物。默认false 67 | "archiveArtifacts": false, 68 | //存储类型 jenkinsStash,dockerRegistry 必填 69 | "jenkinsStash" : [ 70 | //是否激活,默认false 71 | "enable": true, 72 | ], 73 | "dockerRegistry" : [ 74 | //是否激活,默认false 75 | "enable" : false, 76 | "imagePrefix" : "registry.cn-hangzhou.aliyuncs.com/wuzhaozhongguo-app", 77 | //如果不填则使用appName 78 | "imageName" : "prod-xxx-api", 79 | //如果不填则使用日期作为版本号 80 | "imageVersion": "latest", 81 | "dockerfile" : [ 82 | "url" : "git@github.com:daluobai-devops/docker-library.git", 83 | "gitBranch": "main", 84 | "path" : "package-javaweb/openjdk8" 85 | ], 86 | "buildArgs" : ["runOptions": "-Xms128M -Xmx128M", "runArgs": "--spring.profiles.active=dev"] 87 | ], 88 | ], 89 | //发布 90 | "stepsDeploy" : [ 91 | //是否激活,默认true 92 | "enable" : true, 93 | //服务发布服务label 必填 94 | "labels" : ["NODE-DEMO"], 95 | //发布 96 | "stepsJavaWebDeployToService": [ 97 | //是否激活,默认true 98 | "enable" : true, 99 | //java路径 可不填 100 | "javaPath" : "/usr/local/jdk/jdk8/bin/java", 101 | //服务发布路径 必填 102 | "pathRoot" : "/apps/application/", 103 | //JVM参数 [-options] 示例(-Dfile.encoding=UTF-8 -Xms128M -Xmx128M -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005) 104 | "runOptions": "-Xms128M -Xmx128M", 105 | //启动参数 [args...] 示例(-–spring.profiles.active=dev) 106 | "runArgs" : "--spring.profiles.active=dev", 107 | //服务管理方式,支持systemctl,shell,默认systemctl,如果系统没有systemctl命令则使用shell方式 108 | "manageBy" : "systemctl", 109 | ], 110 | //发布到tomcat 111 | "stepsTomcatDeploy" : [ 112 | //是否激活,默认true 113 | "enable" : false, 114 | //工作目录 必选。备份用 115 | "tomcatHome": "/usr/local/tomcat", 116 | //包发布路径 必填 117 | "deployPath": "/usr/local/tomcat/webapps/", 118 | //重启脚本,可不填 119 | "command" : "cd /usr/local/tomcat/bin/ && ./shutdown.sh && sleep 1000 && ./startup.sh" 120 | ], 121 | 122 | //就绪探针 可不填,检查服务是否启动成功,如果启动成功则认为服务发布成功,如果不填则不检查.探针类型,支持http,tcp,cmd. 123 | "readinessProbe" : [ 124 | //检查端口是否监听,如果监听则认为发布成功,如果不填则不检查 可不填 125 | tcp : [ 126 | //是否激活,默认true 127 | "enable": true, 128 | //探针端口 129 | "port" : 8080 130 | ], 131 | //访问http地址,http状态码返回200则认为发布成功,如果不填则不检查 可不填 132 | http : [ 133 | //是否激活,默认true 134 | "enable" : false, 135 | //探针路径, 必填 136 | "path" : "/actuator/health", 137 | //探针端口, 必填 138 | "port" : 8080, 139 | //探针超时时间,单位秒,默认5秒 可不填 140 | "timeout": 5 141 | ], 142 | //执行命令,以退出状态码判断是否成功 可不填 143 | cmd : [ 144 | //是否激活,默认true 145 | "enable" : false, 146 | //探针命令,如果type为cmd则必填 必填 147 | "command": "curl -s 'http://localhost:8080/actuator/health' | grep -q 'UP' && echo 0 || echo 1", 148 | //探针超时时间,单位秒,默认5秒 可不填 149 | "timeout": 5 150 | ], 151 | //探针间隔时间,单位秒,默认5秒 可不填 152 | "period" : 5, 153 | //探针失败次数,如果失败次数达到该值则认为发布失败,默认3次 可不填 154 | "failureThreshold": 40 155 | ], 156 | //所有部署流程执行完成后运行的命令,centos(firewall-cmd --zone=public --add-port=8080/tcp --permanent && firewall-cmd --reload),ubuntu(ufw allow 8080/tcp && ufw reload) 157 | "afterRunCMD" : "" 158 | ] 159 | ], 160 | //默认配置 161 | "DEFAULT_CONFIG" : [ 162 | "docker": [ 163 | "registry": [ 164 | "domain": "docker.io" 165 | ] 166 | ] 167 | ], 168 | // //继承配置 169 | "CONFIG_EXTEND" : [ 170 | //配置文件完整路径configType:path,支持URL,HOST_PATH,RESOURCES,默认RESOURCES. 必填. 171 | "configFullPath": "RESOURCES:config/config.json", 172 | ] 173 | ] 174 | @Library('jenkins-shared-library') _ 175 | deployJavaWeb(customConfig) -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/utils/JsonUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.utils 2 | 3 | import groovy.json.JsonOutput 4 | import groovy.json.JsonSlurper 5 | import groovy.json.JsonSlurperClassic 6 | 7 | /** 8 | * @author daluobai@outlook.com 9 | * version 1.0.0 10 | * @title JSON工具类 - 参考hutool JSONUtil 11 | * @description https://github.com/daluobai-devops/jenkins-shared-library 12 | * @create 2025/12/13 13 | */ 14 | class JsonUtils implements Serializable { 15 | 16 | /** 17 | * 判断字符串是否为JSON格式 18 | * @param str 字符串 19 | * @return 是否为JSON 20 | */ 21 | static boolean isJson(String str) { 22 | if (StrUtils.isBlank(str)) { 23 | return false 24 | } 25 | 26 | str = str.trim() 27 | return (str.startsWith("{") && str.endsWith("}")) || (str.startsWith("[") && str.endsWith("]")) 28 | } 29 | 30 | /** 31 | * 判断字符串是否为JSON对象 32 | * @param str 字符串 33 | * @return 是否为JSON对象 34 | */ 35 | static boolean isJsonObj(String str) { 36 | if (StrUtils.isBlank(str)) { 37 | return false 38 | } 39 | 40 | str = str.trim() 41 | return str.startsWith("{") && str.endsWith("}") 42 | } 43 | 44 | /** 45 | * 判断字符串是否为JSON数组 46 | * @param str 字符串 47 | * @return 是否为JSON数组 48 | */ 49 | static boolean isJsonArray(String str) { 50 | if (StrUtils.isBlank(str)) { 51 | return false 52 | } 53 | 54 | str = str.trim() 55 | return str.startsWith("[") && str.endsWith("]") 56 | } 57 | 58 | /** 59 | * JSON字符串转为对象 60 | * @param jsonStr JSON字符串 61 | * @return 对象(可能是Map或List) 62 | */ 63 | static Object parse(String jsonStr) { 64 | if (StrUtils.isBlank(jsonStr)) { 65 | return null 66 | } 67 | 68 | try { 69 | return new JsonSlurperClassic().parseText(jsonStr) 70 | } catch (Exception e) { 71 | throw new RuntimeException("JSON解析失败", e) 72 | } 73 | } 74 | 75 | /** 76 | * JSON字符串转为Map 77 | * @param jsonStr JSON字符串 78 | * @return Map对象 79 | */ 80 | static Map parseObj(String jsonStr) { 81 | Object result = parse(jsonStr) 82 | if (result instanceof Map) { 83 | return (Map) result 84 | } 85 | throw new IllegalArgumentException("JSON字符串不是一个对象") 86 | } 87 | 88 | /** 89 | * JSON字符串转为List 90 | * @param jsonStr JSON字符串 91 | * @return List对象 92 | */ 93 | static List parseArray(String jsonStr) { 94 | Object result = parse(jsonStr) 95 | if (result instanceof List) { 96 | return (List) result 97 | } 98 | throw new IllegalArgumentException("JSON字符串不是一个数组") 99 | } 100 | 101 | /** 102 | * 对象转JSON字符串 103 | * @param obj 对象 104 | * @return JSON字符串 105 | */ 106 | static String toJsonStr(Object obj) { 107 | if (obj == null) { 108 | return "null" 109 | } 110 | 111 | try { 112 | return JsonOutput.toJson(obj) 113 | } catch (Exception e) { 114 | throw new RuntimeException("对象转JSON失败", e) 115 | } 116 | } 117 | 118 | /** 119 | * 对象转JSON字符串(格式化) 120 | * @param obj 对象 121 | * @return 格式化的JSON字符串 122 | */ 123 | static String toJsonPrettyStr(Object obj) { 124 | if (obj == null) { 125 | return "null" 126 | } 127 | 128 | try { 129 | return JsonOutput.prettyPrint(JsonOutput.toJson(obj)) 130 | } catch (Exception e) { 131 | throw new RuntimeException("对象转JSON失败", e) 132 | } 133 | } 134 | 135 | /** 136 | * JSONObject类 - 包装Map提供便捷方法 137 | */ 138 | static class JSONObject { 139 | private Map map 140 | 141 | JSONObject() { 142 | this.map = [:] 143 | } 144 | 145 | JSONObject(Map map) { 146 | this.map = map ?: [:] 147 | } 148 | 149 | JSONObject(String jsonStr) { 150 | this.map = parseObj(jsonStr) 151 | } 152 | 153 | /** 154 | * 获取字符串值 155 | */ 156 | String getStr(String key) { 157 | Object value = map.get(key) 158 | return value == null ? null : value.toString() 159 | } 160 | 161 | /** 162 | * 获取整数值 163 | */ 164 | Integer getInt(String key) { 165 | Object value = map.get(key) 166 | if (value == null) { 167 | return null 168 | } 169 | if (value instanceof Number) { 170 | return ((Number) value).intValue() 171 | } 172 | return Integer.parseInt(value.toString()) 173 | } 174 | 175 | /** 176 | * 获取长整数值 177 | */ 178 | Long getLong(String key) { 179 | Object value = map.get(key) 180 | if (value == null) { 181 | return null 182 | } 183 | if (value instanceof Number) { 184 | return ((Number) value).longValue() 185 | } 186 | return Long.parseLong(value.toString()) 187 | } 188 | 189 | /** 190 | * 获取布尔值 191 | */ 192 | Boolean getBool(String key) { 193 | Object value = map.get(key) 194 | if (value == null) { 195 | return null 196 | } 197 | if (value instanceof Boolean) { 198 | return (Boolean) value 199 | } 200 | return Boolean.parseBoolean(value.toString()) 201 | } 202 | 203 | /** 204 | * 获取双精度浮点数值 205 | */ 206 | Double getDouble(String key) { 207 | Object value = map.get(key) 208 | if (value == null) { 209 | return null 210 | } 211 | if (value instanceof Number) { 212 | return ((Number) value).doubleValue() 213 | } 214 | return Double.parseDouble(value.toString()) 215 | } 216 | 217 | /** 218 | * 获取JSONObject 219 | */ 220 | JSONObject getJSONObject(String key) { 221 | Object value = map.get(key) 222 | if (value == null) { 223 | return null 224 | } 225 | if (value instanceof Map) { 226 | return new JSONObject((Map) value) 227 | } 228 | throw new IllegalArgumentException("值不是JSONObject类型") 229 | } 230 | 231 | /** 232 | * 获取JSONArray 233 | */ 234 | JSONArray getJSONArray(String key) { 235 | Object value = map.get(key) 236 | if (value == null) { 237 | return null 238 | } 239 | if (value instanceof List) { 240 | return new JSONArray((List) value) 241 | } 242 | throw new IllegalArgumentException("值不是JSONArray类型") 243 | } 244 | 245 | /** 246 | * 设置值 247 | */ 248 | JSONObject set(String key, Object value) { 249 | map.put(key, value) 250 | return this 251 | } 252 | 253 | /** 254 | * 获取值 255 | */ 256 | Object get(String key) { 257 | return map.get(key) 258 | } 259 | 260 | /** 261 | * 判断是否包含key 262 | */ 263 | boolean containsKey(String key) { 264 | return map.containsKey(key) 265 | } 266 | 267 | /** 268 | * 转为JSON字符串 269 | */ 270 | String toString() { 271 | return toJsonStr(map) 272 | } 273 | 274 | /** 275 | * 获取底层Map 276 | */ 277 | Map toMap() { 278 | return new HashMap<>(map) 279 | } 280 | } 281 | 282 | /** 283 | * JSONArray类 - 包装List提供便捷方法 284 | */ 285 | static class JSONArray { 286 | private List list 287 | 288 | JSONArray() { 289 | this.list = [] 290 | } 291 | 292 | JSONArray(List list) { 293 | this.list = list ?: [] 294 | } 295 | 296 | JSONArray(String jsonStr) { 297 | this.list = parseArray(jsonStr) 298 | } 299 | 300 | /** 301 | * 获取指定索引的元素 302 | */ 303 | Object get(int index) { 304 | return list.get(index) 305 | } 306 | 307 | /** 308 | * 获取字符串值 309 | */ 310 | String getStr(int index) { 311 | Object value = list.get(index) 312 | return value == null ? null : value.toString() 313 | } 314 | 315 | /** 316 | * 获取整数值 317 | */ 318 | Integer getInt(int index) { 319 | Object value = list.get(index) 320 | if (value == null) { 321 | return null 322 | } 323 | if (value instanceof Number) { 324 | return ((Number) value).intValue() 325 | } 326 | return Integer.parseInt(value.toString()) 327 | } 328 | 329 | /** 330 | * 获取JSONObject 331 | */ 332 | JSONObject getJSONObject(int index) { 333 | Object value = list.get(index) 334 | if (value == null) { 335 | return null 336 | } 337 | if (value instanceof Map) { 338 | return new JSONObject((Map) value) 339 | } 340 | throw new IllegalArgumentException("值不是JSONObject类型") 341 | } 342 | 343 | /** 344 | * 添加元素 345 | */ 346 | JSONArray add(Object value) { 347 | list.add(value) 348 | return this 349 | } 350 | 351 | /** 352 | * 获取大小 353 | */ 354 | int size() { 355 | return list.size() 356 | } 357 | 358 | /** 359 | * 转为JSON字符串 360 | */ 361 | String toString() { 362 | return toJsonStr(list) 363 | } 364 | 365 | /** 366 | * 获取底层List 367 | */ 368 | List toList() { 369 | return new ArrayList<>(list) 370 | } 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /src/com/daluobai/jenkinslib/utils/HttpUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.daluobai.jenkinslib.utils 2 | 3 | /** 4 | * @author daluobai@outlook.com 5 | * version 1.0.0 6 | * @title HTTP工具类 - 参考hutool HttpUtil 7 | * @description https://github.com/daluobai-devops/jenkins-shared-library 8 | * @create 2025/12/13 9 | */ 10 | class HttpUtils implements Serializable { 11 | 12 | /** 13 | * 发送GET请求 14 | * @param url 请求URL 15 | * @param timeout 超时时间(毫秒) 16 | * @return 响应内容 17 | */ 18 | static String get(String url, int timeout = 30000) { 19 | AssertUtils.notBlank(url, "URL不能为空") 20 | 21 | try { 22 | URL urlObject = new URL(url) 23 | HttpURLConnection connection = (HttpURLConnection) urlObject.openConnection() 24 | 25 | connection.setRequestMethod("GET") 26 | connection.setConnectTimeout(timeout) 27 | connection.setReadTimeout(timeout) 28 | connection.setRequestProperty("User-Agent", "Jenkins-Shared-Library/1.0") 29 | 30 | int responseCode = connection.getResponseCode() 31 | if (responseCode == HttpURLConnection.HTTP_OK) { 32 | return connection.getInputStream().getText("UTF-8") 33 | } else { 34 | throw new RuntimeException("HTTP请求失败,响应码: " + responseCode) 35 | } 36 | } catch (Exception e) { 37 | throw new RuntimeException("GET请求失败: " + url, e) 38 | } 39 | } 40 | 41 | /** 42 | * 发送GET请求(带参数) 43 | * @param url 请求URL 44 | * @param params 参数Map 45 | * @param timeout 超时时间(毫秒) 46 | * @return 响应内容 47 | */ 48 | static String get(String url, Map params, int timeout = 30000) { 49 | AssertUtils.notBlank(url, "URL不能为空") 50 | 51 | if (params != null && !params.isEmpty()) { 52 | StringBuilder urlBuilder = new StringBuilder(url) 53 | if (!url.contains("?")) { 54 | urlBuilder.append("?") 55 | } else if (!url.endsWith("&")) { 56 | urlBuilder.append("&") 57 | } 58 | 59 | params.each { key, value -> 60 | urlBuilder.append(URLEncoder.encode(key, "UTF-8")) 61 | .append("=") 62 | .append(URLEncoder.encode(value ?: "", "UTF-8")) 63 | .append("&") 64 | } 65 | 66 | url = urlBuilder.substring(0, urlBuilder.length() - 1) 67 | } 68 | 69 | return get(url, timeout) 70 | } 71 | 72 | /** 73 | * 发送POST请求(表单) 74 | * @param url 请求URL 75 | * @param params 参数Map 76 | * @param timeout 超时时间(毫秒) 77 | * @return 响应内容 78 | */ 79 | static String post(String url, Map params = [:], int timeout = 30000) { 80 | AssertUtils.notBlank(url, "URL不能为空") 81 | 82 | try { 83 | URL urlObject = new URL(url) 84 | HttpURLConnection connection = (HttpURLConnection) urlObject.openConnection() 85 | 86 | connection.setRequestMethod("POST") 87 | connection.setConnectTimeout(timeout) 88 | connection.setReadTimeout(timeout) 89 | connection.setDoOutput(true) 90 | connection.setRequestProperty("User-Agent", "Jenkins-Shared-Library/1.0") 91 | connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded") 92 | 93 | if (params != null && !params.isEmpty()) { 94 | StringBuilder postData = new StringBuilder() 95 | params.each { key, value -> 96 | if (postData.length() > 0) { 97 | postData.append("&") 98 | } 99 | postData.append(URLEncoder.encode(key, "UTF-8")) 100 | .append("=") 101 | .append(URLEncoder.encode(value ?: "", "UTF-8")) 102 | } 103 | 104 | connection.getOutputStream().write(postData.toString().getBytes("UTF-8")) 105 | } 106 | 107 | int responseCode = connection.getResponseCode() 108 | if (responseCode == HttpURLConnection.HTTP_OK) { 109 | return connection.getInputStream().getText("UTF-8") 110 | } else { 111 | throw new RuntimeException("HTTP请求失败,响应码: " + responseCode) 112 | } 113 | } catch (Exception e) { 114 | throw new RuntimeException("POST请求失败: " + url, e) 115 | } 116 | } 117 | 118 | /** 119 | * 发送POST请求(JSON) 120 | * @param url 请求URL 121 | * @param jsonBody JSON字符串 122 | * @param timeout 超时时间(毫秒) 123 | * @return 响应内容 124 | */ 125 | static String postJson(String url, String jsonBody, int timeout = 30000) { 126 | AssertUtils.notBlank(url, "URL不能为空") 127 | 128 | try { 129 | URL urlObject = new URL(url) 130 | HttpURLConnection connection = (HttpURLConnection) urlObject.openConnection() 131 | 132 | connection.setRequestMethod("POST") 133 | connection.setConnectTimeout(timeout) 134 | connection.setReadTimeout(timeout) 135 | connection.setDoOutput(true) 136 | connection.setRequestProperty("User-Agent", "Jenkins-Shared-Library/1.0") 137 | connection.setRequestProperty("Content-Type", "application/json;charset=utf-8") 138 | 139 | if (StrUtils.isNotBlank(jsonBody)) { 140 | connection.getOutputStream().write(jsonBody.getBytes("UTF-8")) 141 | } 142 | 143 | int responseCode = connection.getResponseCode() 144 | if (responseCode == HttpURLConnection.HTTP_OK) { 145 | return connection.getInputStream().getText("UTF-8") 146 | } else { 147 | throw new RuntimeException("HTTP请求失败,响应码: " + responseCode) 148 | } 149 | } catch (Exception e) { 150 | throw new RuntimeException("POST JSON请求失败: " + url, e) 151 | } 152 | } 153 | 154 | /** 155 | * 下载文件 156 | * @param url 文件URL 157 | * @param destFile 目标文件 158 | * @param timeout 超时时间(毫秒) 159 | */ 160 | static void downloadFile(String url, File destFile, int timeout = 60000) { 161 | AssertUtils.notBlank(url, "URL不能为空") 162 | AssertUtils.notNull(destFile, "目标文件不能为null") 163 | 164 | try { 165 | // 确保父目录存在 166 | File parent = destFile.getParentFile() 167 | if (parent != null && !parent.exists()) { 168 | parent.mkdirs() 169 | } 170 | 171 | URL urlObject = new URL(url) 172 | HttpURLConnection connection = (HttpURLConnection) urlObject.openConnection() 173 | 174 | connection.setRequestMethod("GET") 175 | connection.setConnectTimeout(timeout) 176 | connection.setReadTimeout(timeout) 177 | connection.setRequestProperty("User-Agent", "Jenkins-Shared-Library/1.0") 178 | 179 | int responseCode = connection.getResponseCode() 180 | if (responseCode == HttpURLConnection.HTTP_OK) { 181 | destFile.withOutputStream { os -> 182 | connection.getInputStream().withStream { is -> 183 | os << is 184 | } 185 | } 186 | } else { 187 | throw new RuntimeException("下载文件失败,响应码: " + responseCode) 188 | } 189 | } catch (Exception e) { 190 | throw new RuntimeException("下载文件失败: " + url, e) 191 | } 192 | } 193 | 194 | /** 195 | * HttpRequest类 - 链式调用 196 | */ 197 | static class HttpRequest { 198 | private String url 199 | private String method = "GET" 200 | private Map headers = [:] 201 | private String body 202 | private int timeout = 30000 203 | 204 | HttpRequest(String url) { 205 | this.url = url 206 | } 207 | 208 | static HttpRequest post(String url) { 209 | HttpRequest request = new HttpRequest(url) 210 | request.method = "POST" 211 | return request 212 | } 213 | 214 | static HttpRequest get(String url) { 215 | return new HttpRequest(url) 216 | } 217 | 218 | HttpRequest contentType(String contentType) { 219 | headers.put("Content-Type", contentType) 220 | return this 221 | } 222 | 223 | HttpRequest header(String name, String value) { 224 | headers.put(name, value) 225 | return this 226 | } 227 | 228 | HttpRequest body(String body) { 229 | this.body = body 230 | return this 231 | } 232 | 233 | HttpRequest timeout(int timeout) { 234 | this.timeout = timeout 235 | return this 236 | } 237 | 238 | HttpResponse execute() { 239 | try { 240 | URL urlObject = new URL(url) 241 | HttpURLConnection connection = (HttpURLConnection) urlObject.openConnection() 242 | 243 | connection.setRequestMethod(method) 244 | connection.setConnectTimeout(timeout) 245 | connection.setReadTimeout(timeout) 246 | connection.setRequestProperty("User-Agent", "Jenkins-Shared-Library/1.0") 247 | 248 | // 设置请求头 249 | headers.each { key, value -> 250 | connection.setRequestProperty(key, value) 251 | } 252 | 253 | // 写入请求体 254 | if (StrUtils.isNotBlank(body)) { 255 | connection.setDoOutput(true) 256 | connection.getOutputStream().write(body.getBytes("UTF-8")) 257 | } 258 | 259 | int responseCode = connection.getResponseCode() 260 | String responseBody = "" 261 | 262 | if (responseCode == HttpURLConnection.HTTP_OK) { 263 | responseBody = connection.getInputStream().getText("UTF-8") 264 | } else { 265 | // 尝试读取错误响应 266 | try { 267 | responseBody = connection.getErrorStream()?.getText("UTF-8") ?: "" 268 | } catch (Exception ignored) { 269 | } 270 | } 271 | 272 | return new HttpResponse(responseCode, responseBody) 273 | } catch (Exception e) { 274 | throw new RuntimeException("HTTP请求失败: " + url, e) 275 | } 276 | } 277 | } 278 | 279 | /** 280 | * HttpResponse类 281 | */ 282 | static class HttpResponse { 283 | private int status 284 | private String body 285 | 286 | HttpResponse(int status, String body) { 287 | this.status = status 288 | this.body = body 289 | } 290 | 291 | int getStatus() { 292 | return status 293 | } 294 | 295 | String body() { 296 | return body 297 | } 298 | 299 | boolean isOk() { 300 | return status == HttpURLConnection.HTTP_OK 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /resources/config/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 46 | 49 | 55 | 56 | 64 | 65 | 72 | 73 | 78 | 79 | 83 | 84 | 85 | 86 | 91 | 92 | 106 | 107 | 108 | 112 | 113 | 126 | 127 | 134 | 135 | 136 | 147 | 148 | 160 | 161 | nexus-aliyun 162 | central 163 | Nexus aliyun 164 | https://maven.aliyun.com/nexus/content/groups/public 165 | 166 | 167 | maven-default-http-blocker 168 | external:http:* 169 | Pseudo repository to mirror external repositories initially using HTTP. 170 | http://0.0.0.0/ 171 | true 172 | 173 | 174 | 175 | 196 | 197 | 226 | 227 | 261 | 262 | 263 | 271 | 272 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------