组件 | 版本 |
---|---|
Ubuntu | 20.04 |
Jenkins | 2.319.1 |
Bitbucket |
pipeline {
/*Jenkins节点,any代表所有*/
//agent any
agent {
node {
//在label为dev1的节点进行部署
label 'dev1'
}
}
/*环境变量,类似全局变量*/
environment {
BUILD_USER = "" //项目构建者
GIT_COMMIT_MSG = "" //GIT提交信息
GIT_COMMIT_ID = "" //GIT提交ID,可用于标识版本
/*部署配置*/
POM_PATH = "${env.WORKSPACE}/pom.xml" //配置文件路径
POM_ARTIFACTID = "" //项目名称
POM_VERSION = "" //项目版本
POM_PROJECT_NAME = "" //上一版项目全称,用于杀死上一版本进程:POM_ARTIFACTID+'-'+POM_VERSION+'.jar'。值为空则读取Pom.xml中上个版本项目名称,值不为空则指定杀死进程。
JAR_NAME = "" //jar包名称
JAR_PATH = "${env.WORKSPACE}/target" //生成的jar包路径
/*部署配置*/
JAR_WORK_PATH = "你要运行jar包的路径" //运行jar的工作路径,统一管理,并需要提前创建好
/*部署配置*/
LOG_PATH = "你要放日志的路径" //日志路径
}
/*Jenkins自动构建触发器*/
triggers{
//每5分钟判断一次代码是否有变化
pollSCM('H/5 * * * *')
}
/*构建阶段*/
stages {
/*准备阶段:拉取代码、定义全局变量等*/
stage('Preparation') {
steps {
//使用build user vars插件,获取构建执行者
wrap([$class: 'BuildUser']) {
script {
BUILD_USER = "${env.BUILD_USER}" //将构建执行者注入到环境变量中,方便最后通知使用
}
}
/*部署配置*/
/** 从Bitbucket上拉取分支
* @url git地址
* @branch 分支名称
* @credentialsId Jenkins凭证Id,用于远程访问
*/
git(url: 'https://[email protected]/sleetdream/demo-hello.git', branch: 'master', credentialsId: 'sleetdream')
script {
//执行Git命令获取Git相关信息赋值给全局变量,returnStdout返回命令结果
GIT_COMMIT_MSG = sh(script: 'git log -1 --pretty=%B ${GIT_COMMIT}', returnStdout: true).trim()
GIT_COMMIT_ID = sh(script: 'git rev-parse --short HEAD', returnStdout: true ).trim()
}
/*读取pom.xml文件,设置全局变量*/
readRom()
/**
* 提交修改的Pom.xml文件到Bitbucket
* 1.获取Jenkins凭证,将凭证中的username赋予变量GIT_USERNAME,password赋予变量GIT_PASSWORD
* 2.运行git脚本,提交代码并push到Bitbucket
*/
withCredentials([usernamePassword(credentialsId: 'sleetdream', usernameVariable: "GIT_USERNAME", passwordVariable: "GIT_PASSWORD")]) {
sh('git config --global user.name "sleetdream"') //设置Git本地全局用户名
sh('git config --global user.email "[email protected]"') //设置Git本地全局邮箱
sh('git commit -a -m "Jenkins自动修改版本"') //参数说明:-a -m 只提交跟踪过的文件,即修改过的文件
/*部署配置*/
sh('git push https://${GIT_USERNAME}:${GIT_PASSWORD}@bitbucket.org/sleetdream/demo-hello.git') //使用凭证中的Username和Password提交代码
}
/*部署配置*/
//再次拉取代码,防止Jenkins因Push触发自动编译,导致自动编译与版本修改的死循环
git(url: 'https://[email protected]/sleetdream/demo-hello.git', branch: 'master', credentialsId: 'sleetdream')
}
}
/*构建阶段*/
stage('Build'){
steps{
/**
* 执行maven打包
* -B --batch-mode 在非交互(批处理)模式下运行(该模式下,当Mven需要输入时,它不会停下来接受用户的输入,而是使用合理的默认值)
* 打包时跳过JUnit测试用例
* -DskipTests 不执行测试用例,但编译测试用例类生成相应的class文件至target/test-classes下
* -Dmaven.test.skip=true,不执行测试用例,也不编译测试用例类
**/
sh 'mvn -B -DskipTests clean package'
}
}
/*部署阶段*/
stage('Deliver') {
steps {
withEnv(['JENKINS_NODE_COOKIE=background_job']) {
sh """
# 停止服务并杀死进程
pid=\$(ps -ef | grep ${POM_ARTIFACTID} | grep -v grep | awk \'{ print \$2 }\')
if [ -z "\$pid" ]
then
echo ${POM_ARTIFACTID} is already stopped
else
echo kill ${POM_ARTIFACTID}
echo kill -2 \${pid}
kill -15 \${pid}
fi
# kill需要一定时间,等待10秒
sleep 10
# 创建默认路径
mkdir -p ${JAR_WORK_PATH}
# 移动打包文件
cp -f ${JAR_PATH}/${JAR_NAME} ${JAR_WORK_PATH}
# 将工作目录切换到日志路径执行程序
cd ${LOG_PATH}
# /dev/null 所有写入它的内容都会永远丢失
nohup java -jar ${JAR_WORK_PATH}/${JAR_NAME} >/dev/null 2>>${JAR_WORK_PATH}/sys_error.log &
"""
}
}
}
}
post {
success {
dingtalk (
robot: "dev1_demo",
type:'ACTION_CARD',
atAll: false,
title: "构建成功:${env.JOB_NAME}",
//messageUrl: 'xxxx',
text: [
"### [${env.JOB_NAME}](${env.JOB_URL}) ",
'---',
"- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
'- 状态:成功',
"- 持续时间:${currentBuild.durationString}".split("and counting")[0],
"- 执行人:${BUILD_USER}",
"- 提交日志: ${GIT_COMMIT_MSG}",
]
)
}
failure{
dingtalk (
robot: "dev1_demo",
type:'ACTION_CARD',
atAll: false,
title: "构建失败:${env.JOB_NAME}",
//messageUrl: 'xxxx',
text: [
"### [${env.JOB_NAME}](${env.JOB_URL}) ",
'---',
"- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
'- 状态:失败',
"- 持续时间:${currentBuild.durationString}".split("and counting")[0],
"- 执行人:${BUILD_USER}",
"- 提交日志: ${GIT_COMMIT_MSG}",
]
)
}
}
}
/**
* 读取配置文件,获取信息
* @return
*/
def readRom(){
def pom = readMavenPom file: "${POM_PATH}" //使用Jenkins插件pipeline-utility-steps读取pom.xml文件,使用方法详见https://www.jenkins.io/doc/pipeline/steps/pipeline-utility-steps/#readmavenpom-read-a-maven-project-file
if(!POM_PROJECT_NAME){
POM_PROJECT_NAME = "${pom.artifactId}" + '-' + "${pom.version}" + '.jar'
}
println("当前中的版本:${pom.version}")
POM_VERSION = modifyVersion("${pom.version}") //修改版本
pom.version = POM_VERSION
println("更新后的版本:${pom.version}")
writeMavenPom model: pom //使用Jenkins插件pipeline-utility-steps写入pom.xml文件
//设置全局变量
POM_ARTIFACTID = "${pom.artifactId}"
JAR_WORK_PATH += POM_ARTIFACTID + '/'
JAR_NAME = POM_ARTIFACTID + '-' + POM_VERSION + '.jar'
}
/**
* 修改Version信息
* @return version
*/
def modifyVersion(String version) {
def flag = false
if(version.endsWith('-SNAPSHOT')){
version = version.replace('-SNAPSHOT','')
flag = true
}
def number = version.split('\\.')
def num0 = number[0]
def num1 = number[1]
int num2 = number[2] as Integer
num2 += 1
version = num0+'.'+num1+'.'+num2 +'.'+ "${GIT_COMMIT_ID}"
if(flag){
version += '-SNAPSHOT'
}
return version
}
/*Jenkins节点,any代表所有*/
//agent any
agent {
node {
//在label为dev1的节点进行部署
label 'dev1'
}
}
官方文档
在Jenkins上新建节点,将其标签设置为dev1,下载agent.jar在目标服务器上按Jenkins给出的命令启动。
/*环境变量,类似全局变量*/
environment {
BUILD_USER = "" //项目构建者
GIT_COMMIT_MSG = "" //GIT提交信息
GIT_COMMIT_ID = "" //GIT提交ID,可用于标识版本
/*部署配置*/
POM_PATH = "${env.WORKSPACE}/pom.xml" //配置文件路径
POM_ARTIFACTID = "" //项目名称
POM_VERSION = "" //项目版本
POM_PROJECT_NAME = "" //上一版项目全称,用于杀死上一版本进程:POM_ARTIFACTID+'-'+POM_VERSION+'.jar'。值为空则读取Pom.xml中上个版本项目名称,值不为空则指定杀死进程。
JAR_NAME = "" //jar包名称
JAR_PATH = "${env.WORKSPACE}/target" //生成的jar包路径
/*部署配置*/
JAR_WORK_PATH = "你要运行jar包的路径" //运行jar的工作路径,统一管理,并需要提前创建好
/*部署配置*/
LOG_PATH = "你要放日志的路径" //日志路径
}
官方文档
定义Jenkins的环境变量,方便后续部署步骤使用。
//可直接使用环境变量
def path = POM_PATH
sh """
# 需要用${}将变量名称包裹起来
echo ${POM_PROJECT_NAME}
"""
【注意】在sh调用脚本双引号和单引号存在区别,双引号可以使用变量,单引号不能使用变量
linux sh 三个单引号,Shell双引号和单引号有哪些不同
/*Jenkins自动构建触发器*/
triggers{
//每5分钟判断一次代码是否有变化
pollSCM('H/5 * * * *')
}
官方文档
获取Jenkins的构建执行者,用于发送构建通知给Email或钉钉。
//使用build-user-vars-plugin插件,获取构建执行者
wrap([$class: 'BuildUser']) {
script {
BUILD_USER = "${env.BUILD_USER}" //将构建执行者注入到环境变量中,方便最后通知使用
}
}
build-user-vars-plugin插件官方文档
/*部署配置*/
/** 从Bitbucket上拉取分支
* @url git地址
* @branch 分支名称
* @credentialsId Jenkins凭证Id,用于远程访问
*/
git(url: 'https://[email protected]/sleetdream/demo-hello.git', branch: 'master', credentialsId: 'sleetdream')
git插件官方文档
获取构建信息有两种方式,可以直接运行Git命令获取返回值,也可以从环境变量中获取
script {
//执行Git命令获取Git相关信息赋值给全局变量,returnStdout返回命令结果
GIT_COMMIT_MSG = sh(script: 'git log -1 --pretty=%B ${GIT_COMMIT}', returnStdout: true).trim()
GIT_COMMIT_ID = sh(script: 'git rev-parse --short HEAD', returnStdout: true ).trim()
}
@NonCPS
def getChangeString() {
MAX_MSG_LEN = 100
def changeString = ""
echo "Gathering SCM changes"
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
changeString += " -${entry.author} : ${truncated_msg} \n"
}
}
if (!changeString) {
changeString = " - No new changes"
}
return changeString
}
全局变量文档 “你的jenkins地址/pipeline-syntax/globals”
currentBuild.changeSets为环境变量,变量详解
/**
* 读取配置文件,获取信息
* @return
*/
def readRom(){
def pom = readMavenPom file: "${POM_PATH}" //使用Jenkins插件pipeline-utility-steps读取pom.xml文件,使用方法详见https://www.jenkins.io/doc/pipeline/steps/pipeline-utility-steps/#readmavenpom-read-a-maven-project-file
if(!POM_PROJECT_NAME){
POM_PROJECT_NAME = "${pom.artifactId}" + '-' + "${pom.version}" + '.jar'
}
println("当前中的版本:${pom.version}")
POM_VERSION = modifyVersion("${pom.version}") //修改版本
pom.version = POM_VERSION
println("更新后的版本:${pom.version}")
writeMavenPom model: pom //使用Jenkins插件pipeline-utility-steps写入pom.xml文件
//设置全局变量
POM_ARTIFACTID = "${pom.artifactId}"
JAR_WORK_PATH += POM_ARTIFACTID + '/'
JAR_NAME = POM_ARTIFACTID + '-' + POM_VERSION + '.jar'
}
使用Jenkins插件pipeline-utility-steps读写pom.xml
【注意】此处不能使用Groovy自带的XmlParser和XmlSlurper,原因是这两个类使用了java.io.file。凡是涉及java.io.file的方法,均只能在Jenkins主节点执行,就算一开始agent指定了node,该处脚本也只会在master节点执行。
jenkins pipeline 使用groovy操作文件提示java.io.FileNotFoundException: ×××××.txt (No such file or directory)
如下例子笔者已验证,该脚本只会在master节点运行,经常会出现master节点的pom.xml文件路径不存在,出现java.io.FileNotFoundException
/**
* 修改Version信息,并Push文件
* @return
*/
def modifyVersion() {
def doc = new XmlSlurper().parse("${env.WORKSPACE}/pom.xml");
String version = doc.version.text();
def flag = false;
if(version.endsWith('-SNAPSHOT')){
version = version.replace('-SNAPSHOT','');
flag = true;
}
def number = version.split('\\.')
def num0 = number[0];
def num1 = number[1];
int num2 = number[2] as Integer;
num2 += 1;
version = num0+'.'+num1+'.'+num2;
if(flag){
version += '-SNAPSHOT';
}
println(version);
doc.version.replaceBody(version);
def writeFile = new File("${env.WORKSPACE}/pom.xml");
writeFile.write(groovy.xml.XmlUtil.serialize(doc).replaceAll('tag0:', '').replaceAll(':tag0', '') );
return version;
}
使用groovy脚本修改Version,此处可根据需要进行定制
对version进行split时请注意,英文句号.需要双斜杠进行转义
/**
* 修改Version信息
* @return version
*/
def modifyVersion(String version) {
def flag = false
if(version.endsWith('-SNAPSHOT')){
version = version.replace('-SNAPSHOT','')
flag = true
}
def number = version.split('\\.')
def num0 = number[0]
def num1 = number[1]
int num2 = number[2] as Integer
num2 += 1
version = num0+'.'+num1+'.'+num2 +'.'+ "${GIT_COMMIT_ID}"
if(flag){
version += '-SNAPSHOT'
}
return version
}
/**
* 提交修改的Pom.xml文件到Bitbucket
* 1.获取Jenkins凭证,将凭证中的username赋予变量GIT_USERNAME,password赋予变量GIT_PASSWORD
* 2.运行git脚本,提交代码并push到Bitbucket
*/
withCredentials([usernamePassword(credentialsId: 'sleetdream', usernameVariable: "GIT_USERNAME", passwordVariable: "GIT_PASSWORD")]) {
sh('git config --global user.name "sleetdream"') //设置Git本地全局用户名
sh('git config --global user.email "[email protected]"') //设置Git本地全局邮箱
sh('git commit -a -m "Jenkins自动修改版本"') //参数说明:-a -m 只提交跟踪过的文件,即修改过的文件
/*部署配置*/
sh('git push https://${GIT_USERNAME}:${GIT_PASSWORD}@bitbucket.org/sleetdream/demo-hello.git') //使用凭证中的Username和Password提交代码
}
官方示例
Push代码有两种方式:
1.使用Jenkins凭证中的用户名密码
2.使用Jenkins凭证中的SSH密钥
凭证管理官方文档
// This is currently the best way to push a tag (or a branch, etc) from a
// Pipeline job. It's not ideal - https://issues.jenkins-ci.org/browse/JENKINS-28335
// is an open JIRA for getting the GitPublisher Jenkins functionality working
// with Pipeline.
// credentialsId here is the credentials you have set up in Jenkins for pushing
// to that repository using username and password.
withCredentials([usernamePassword(credentialsId: 'git-pass-credentials-ID', passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) {
sh("git tag -a some_tag -m 'Jenkins'")
sh('git push https://${GIT_USERNAME}:${GIT_PASSWORD}@ --tags' )
}
// For SSH private key authentication, try the sshagent step from the SSH Agent plugin.
sshagent (credentials: ['git-ssh-credentials-ID']) {
sh("git tag -a some_tag -m 'Jenkins'")
sh('git push --tags' )
}
代码简析:withCredentials使用凭证,credentialsId凭证ID,凭证中的usernameVariable用户名、passwordVariable密码赋予内部变量GIT_USERNAME、GIT_PASSWORD,在运行git push命令时使用。
//再次拉取代码,防止Jenkins因Push触发自动编译,导致自动编译与版本修改的死循环
git(url: 'https://[email protected]/sleetdream/demo-hello.git', branch: 'master', credentialsId: 'sleetdream')
【注意】如果之前Push过代码,此处必须重新拉取一次。否则Jenkins触发器会认为本地版本与线上版本不一致,造成自动编译与版本修改的死循环
/**
* 执行maven打包
* -B --batch-mode 在非交互(批处理)模式下运行(该模式下,当Mven需要输入时,它不会停下来接受用户的输入,而是使用合理的默认值)
* 打包时跳过JUnit测试用例
* -DskipTests 不执行测试用例,但编译测试用例类生成相应的class文件至target/test-classes下
* -Dmaven.test.skip=true,不执行测试用例,也不编译测试用例类
**/
sh 'mvn -B -DskipTests clean package'
withEnv(['JENKINS_NODE_COOKIE=background_job']) {
sh """
# 停止服务并杀死进程
pid=\$(ps -ef | grep ${POM_ARTIFACTID} | grep -v grep | awk \'{ print \$2 }\')
if [ -z "\$pid" ]
then
echo ${POM_ARTIFACTID} is already stopped
else
echo kill ${POM_ARTIFACTID}
echo kill -2 \${pid}
kill -15 \${pid}
fi
# kill需要一定时间,等待10秒
sleep 10
# 创建默认路径
mkdir -p ${JAR_WORK_PATH}
# 移动打包文件
cp -f ${JAR_PATH}/${JAR_NAME} ${JAR_WORK_PATH}
# 将工作目录切换到日志路径执行程序
cd ${LOG_PATH}
# /dev/null 所有写入它的内容都会永远丢失
nohup java -jar ${JAR_WORK_PATH}/${JAR_NAME} >/dev/null 2>>${JAR_WORK_PATH}/sys_error.log &
"""
}
【注意】在sh调用脚本双引号和单引号存在区别,双引号可以使用变量,单引号不能使用变量
linux sh 三个单引号,Shell双引号和单引号有哪些不同
withEnv([‘JENKINS_NODE_COOKIE=dontkillme’]) 解决杀掉了所有子进程问题
sh """
echo ${POM_ARTIFACTID}
"""
需要对$字符进行转义
sh """
artifactId = 'demo-hello'
echo \${artifactId}
"""
post {
success {
dingtalk (
robot: "dev1_demo",
type:'ACTION_CARD',
atAll: false,
title: "构建成功:${env.JOB_NAME}",
//messageUrl: 'xxxx',
text: [
"### [${env.JOB_NAME}](${env.JOB_URL}) ",
'---',
"- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
'- 状态:成功',
"- 持续时间:${currentBuild.durationString}".split("and counting")[0],
"- 执行人:${BUILD_USER}",
"- 提交日志: ${GIT_COMMIT_MSG}",
]
)
}
failure{
dingtalk (
robot: "dev1_demo",
type:'ACTION_CARD',
atAll: false,
title: "构建失败:${env.JOB_NAME}",
//messageUrl: 'xxxx',
text: [
"### [${env.JOB_NAME}](${env.JOB_URL}) ",
'---',
"- 任务:[${currentBuild.displayName}](${env.BUILD_URL})",
'- 状态:失败',
"- 持续时间:${currentBuild.durationString}".split("and counting")[0],
"- 执行人:${BUILD_USER}",
"- 提交日志: ${GIT_COMMIT_MSG}",
]
)
}
}
Jenkins DingTalk 钉钉通知插件