Jenkins系列之pipeline语法介绍与案例
Jenkins Pipeline 的核心概念:
Pipeline 是一套运行于
Jenkins
上的工作流框架,将原本独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂流程编排与可视化。Pipeline是 Jenkins2.X 的
最核心的特性
,帮助Jenkins 实现从CI到CD与DevOps的转变。Pipeline是一组插件,让
Jenkins
可以实现持续交付管道的落地和实施。
持续交付管道(CD Pipeline)是将软件从版本控制阶段到交付给用户或客户的完整过程的自动化表现。软件的每一次更改(提交到源代码管理系统)都要经过一个复杂的过程才能被发布。
Pipeline提供了一组可扩展的工具,通过Pipeline Domain Specific Language(DSL)syntax可以达到Pipeline as Code(Jenkinsfile存储在项目的源代码库)的目的。
Pipeline入门:
先决条件
要使用Jenkins Pipeline,您将需要:
- Jenkins 2.x或更高版本
- Pipeline插件(请自行在插件管理中安装。)
Pipeline 定义
脚本Pipeline是用Groovy写的 。Groovy相关语法请移步>。
Pipeline支持两种语法:
- Declarative 声明式
- Scripted pipeline 脚本式
如何创建基本Pipeline:
- 直接在Jenkins网页界面中输入脚本。
- 通过创建一个Jenkinsfile可以检入项目的源代码管理库。
用任一方法定义Pipeline的语法是一样的,但是Jenkins支持直接进入Web UI的Pipeline,通常认为最佳实践是在Jenkinsfile Jenkins中直接从源代码控制中加载Pipeline。
在Web UI中定义Pipeline
要在Jenkins Web UI中创建基本Pipeline:
单击Jenkins主页上的New Item。
输入Pipeline的名称,选择Pipeline,然后单击确定。
在脚本文本区域中,输入Pipeline,然后单击保存。
单击立即生成以运行Pipeline,查看控制台输出。
Pipeline几个核心概念:
Stages:阶段组/Stage:阶段
- 一个 Pipeline 有多个 Stage 组成,每个 Stage 包含一组 Step。
- 注意一个 Stage 可以跨多个 Node 执行,即 Stage 实际上是 Step 的逻辑分组。
- 一个Jenkinsfile 可以分为大的阶段,如打包、构建、 部署。测试
- 构建的流程,可以分为这几步,获取源代码,然后打包,构建,进行编译,替换配置文件,编译完打包,进行部署 这个阶段就是stage
Node:节点,一个Node就是一个Jenkins节点,或者是Master,或者是Agent,是执行Step的具体运行环境。
Steps:步骤,Step是最基本的操作单元,小到创建一个目录,大到构建一个Docker镜像,由各类Jenklins Plugin提供,例如:sh ‘make’
Pipeline几个核心关键字:
块(blocks{}):
由大括号括起来的语句,如pipeline{},Section{},parameters{},script{}章节(Sections):
通常包含一个或多个指令或步骤。如 agent 、post、stages、steps指令(Directives):
environment、options、parameters、triggers(触发)、stage、tools、when步骤(Steps):
执行脚本式pipeline:在该语句块内使用script{}agent
必须存在,agent必须在pipeline块内的顶层定义,但stage内是否使用是可选的
参数:any/none/label/node/docker/dockerfile
常用选项 label/cuetomWorkspace/reuseNode
指令名 | 说明 | 作用域 |
---|---|---|
agent | 定义执行任务的代理 | stage 或pipeline |
input | 暂停pipeline,提示输入内容 | stage |
environment | 设置环境变量 | stage或pipeline |
tools | 自动下载并安装指定的工具,并将其加入到PATH变量中 | stage或pipeline |
options | 配置Jenkins pipeline本身,如options{retry(3}},指pipeline失败时再重试2次 | stage 或 pipeline |
build | 触发其他的job | steps |
when | 定义阶段执行的条件 | stage |
triggers | 定义执行pipeline的触发器 | pipeline |
parameters | 执行pipeline前传入一些参数 | pipeline |
parallel | 并行执行多个step | stage |
示例:
agent:
agent { label 'my-label' } agent {
node {
label 'my-label'
customWorkspace '/some/other/path'
}
} agent {
docker {
image 'application_name:verison'
label 'my-label'
args '-v /tmp:/tmp'
}
}
stage间通过stash进行文件共享,即使stage不在同一个执行主机上:
pipeline{
agent none
stages{
stage('stash'){
agent { label "master" }
steps{
writeFile file: "a.txt", text: "$BUILD_NUMBER"
stash name: "abc", includes: "a.txt"
}
}
stage('unstash'){
agent { label "node" }
steps{
script{
unstash("abc")
def content = readFile("a.txt")
echo "${content}"
}
}
}
}
}
steps中的一些操作:
命令名 说明 error 抛出异常,中断整个pipeline timeout timeout闭包内运行的步骤超时时间 waitUntil 一直循环运行闭包内容,直到return true,经常与timeout同时使用 retry 闭包内脚本重复执行次数 sleep 暂停pipeline一段时间,单位为秒 pipeline{
agent any
stages{
stage('stash'){
steps{
timeout(50){
waitUntil{
script{
def r = sh script: 'curl http://xxx', returnStatus: true
return (r == 0)
}
}
}
retry(10){
script{
sh script: 'curl http://xxx', returnStatus: true
}
}
sleep(20)
}
}
}
}
triggers:定时构建
pipeline {
agent any
triggers {
cron('H 9 * * *')
}
}
paramparameters:参数化构建
- pipeline 脚本
pipeline {
agent any
parameters {
choice(name: 'ENV', choices: 'dev\nsit\nuat', description: '环境')
// 或者 choice(name: 'ENV', choices: ['dev','sit',uat'], description: '环境')
string(name: 'PROJECT', defaultValue: 'example-demo', description: '项目')
booleanParam(defaultValue: true, description: '', name: 'BOOLEAN')
text(defaultValue: '''this is a multi-line
string parameter example
''', name: 'MULTI-LINE-STRING')
}
stages {
stage('Hello') {
steps {
echo 'Hello World'
}
}
}
} - web_ui配置:
也可用于选择git分支/tag 进行发布- 添加参数类型为:
- 编写groovy脚本:
替换掉自己的git项目地址!
- 添加参数类型为:
def gettags = ("git ls-remote --heads --tags ssh://git@{ your ip }/xx/project_name.git").execute()
if (ENV.equals("pre")){
gettags = ("git ls-remote --heads --tags ssh://git@{ your ip }/xx/project_name.git").execute()
}
def repoNameList = gettags.text.readLines().collect {
it.split()[1].replaceAll('refs/heads/', '').replaceAll('refs/tags/', '').replaceAll("\\^\\{\\}", '')
}
repoNameList.eachWithIndex { it, i ->
if (it.equals("master")){
repoNameList[i]= "master:selected"
}
}
return repoNameList
效果:
- pipeline 脚本
post:后置操作
Jenkinsfile (Declarative Pipeline)
pipeline {
agent any
stages {
stage('Hello') {
steps {
sh 'ls'
}
post {
always {
echo '步骤Hello体里的post操作'
}
}
}
}
// post部分可以同时包含多种条件块。
post {
always {
echo '永远都会执行'
}
success {
echo '本次构建成功时执行'
}
unstable {
echo '构建状态为不稳定时执行。'
}
failure {
echo '本次构建失败时执行'
}
changed {
echo '只要本次构建状态与上一次构建状态不同就执行。'
}
fixed {
echo '上一次构建状态为失败或不稳定,当前完成状态为成功时执行。'
}
regression {
echo '上一次构建状态为成功,当前构建状态为失败、不稳定或中止时执行。'
}
aborted {
echo '当前执行结果是中止状态时(一般为人为中止)执行。'
}
cleanup {
echo '清理条件块。不论当前完成状态是什么,在其他所有条件块执行完成后都执行'}
}
}
效果:
案例:
- 执行自动化测试脚本并通过飞书卡片消息发送执行结果到飞书群:
先上效果图:
图中用例执行结果需在代码中获取pytest执行结果统计
需在最外层conftest中加以下代码:from _pytest import terminal def pytest_terminal_summary(terminalreporter):
"""收集测试结果,注:跟xdist插件不兼容"""
# print(terminalreporter.stats)
total = terminalreporter._numcollected - len(terminalreporter.stats.get('deselected', [])) # 收集总数-未选中用例数
passed = len([i for i in terminalreporter.stats.get('passed', []) if i.when == 'call'])
failed = len([i for i in terminalreporter.stats.get('failed', []) if i.when == 'call'])
error = len([i for i in terminalreporter.stats.get('error', []) if i.when != 'teardown'])
skipped = len([i for i in terminalreporter.stats.get('skipped', []) if i.when != 'teardown'])
pass_rate = round(passed / (total - skipped) * 100, 2) # terminalreporter._sessionstarttime 会话开始时间
duration = time.time() - terminalreporter._sessionstarttime
# 将结果写入到一个文件中,后续再读取出来
result_path = os.path.join(PROJECT_DIR, "reports/result.json")
with open(result_path, "w")as fp:
fp.write(
str({"total": total, "passed": passed, "failed": failed, "error": error, "skipped": skipped,
"pass_rate": pass_rate, "duration": round(duration, 3)}))
如何发送卡片消息到飞书群(企微/钉钉同理):
创建群机器人
保存好这个webhook接收地址!
官网查找开发者文档,定制消息样式。
本文使用消息卡片,自己按需使用
编写发送消息脚本
#!/bin/env python3
# -*- coding: utf-8 -*-
# desc:封装Jenkins构建结果发送飞书通知 import requests
import sys class LarkBotReq(object):
# 机器人webhook接收地址
API_ENDPOINT = "https://open.feishu.cn/open-apis/bot/v2/hook/{}" def __init__(self, bot_id=None, current_result=None, result_space=None):
# 获取用例执行结果
with open(result_space, 'r') as fp:
result = fp.read()
result = eval(result)
self.BUILD_MSG_TMPL = '''*构建结果:*
部署环境: **{env}**
当前版本: **{target}**
构建结果: **{current_result}**
构建发起: **{build_user}**
持续时间: **{duration}**
构建日志: [点击查看详情]({build_url}console)
提交信息: [点击查看详情]({git_url}/commit/{env_commit})
'''
self.RUN_MSG_TMPL = f'''\n --------------\n*本次执行结果:*
总用例数: **{result["total"]}**
通过用例: **{result['passed']}**
失败用例: **{result["failed"]}**
跳过用例: **{result['skipped']}**
异常用例:**{result['error']}**
通过率(%): **{result['pass_rate']}**
执行耗时:**{result['duration']}s**
'''
self.COMMIT_MSG_TMPL = '''\n --------------\n*历史更新记录:*
{git_commit_msg}
''' self.url = self.API_ENDPOINT.format(bot_id)
self.body = None self.msg_tmpl = self.BUILD_MSG_TMPL + self.COMMIT_MSG_TMPL + self.RUN_MSG_TMPL def post_json(self):
# Send http request with json body
headers = {
"Content-Type": "application/json; charset=utf-8"
}
res = requests.post(self.url, json=self.body, headers=headers)
return res.text def formatter(self, **kwargs):
self.body = {
"msg_type": "interactive",
"card": {
"config": {
"wide_screen_mode": True
},
"header": {
"title": {
"tag": "plain_text",
"content": "{project} 项目构建信息".format(**kwargs)
},
# 控制卡片颜色
"template": "green" if result.get('pass_rate') >= 100 else "red"
},
"elements": [
{
"tag": "markdown", # 使用markdown格式
"content": self.msg_tmpl.format(**kwargs),
}
]
}
} if __name__ == '__main__':
bot = LarkBotReq(bot_id=sys.argv[1], current_result=sys.argv[2], result_space=sys.argv[12])
bot.formatter(
project=sys.argv[3],
env=sys.argv[4],
target=sys.argv[5],
current_result=sys.argv[2],
build_user=sys.argv[6],
duration=sys.argv[7],
build_url=sys.argv[8],
git_url=sys.argv[9],
env_commit=sys.argv[10],
git_commit_msg=sys.argv[11]
)
print(bot.post_json()) """
调用方:
/usr/local/python3/bin/python3 send_msg_2_lark.py '你的机器人id' ' success' 'project_name' 'env' 'master' '{buildUser}' '{持续时间}' '{build_url}' '{git_url}' '{commit_id}' '{commit_msg}' 'reports/result.json' """
把上面这个发送消息脚本放到jenkins服务器的某个目录里,如:
/data/deploy/notify
配置jenkins
配置上文提到的获取git分支/tag groovy脚本
编写pipeline脚本
下文中引用的credentialsId配置credentialsId参阅往期>
下文中的测试报告插件使用的是pytest-html,请自行去插件管理下载HTML Publisher;如果用的allure替换掉下文的pipeline中publishHTML配置。
下文中所引用的env.xx变量是Jenkins提供的全局变量,请自行查阅;你的job地址后拼接
/pipeline-syntax/globals
即可查阅。
或:
def project_url = 'ssh://git@{your host}/xx/project_name.git' // 用于拉代码
def GIT_URL = 'https://{your host}/xx/project_name' // 用于发送的消息卡片跳转
def credentialsId = 'your credentialsId' // git登陆凭证id
pipeline {
agent any
parameters {
choice(name: 'ENV', choices: ['dev','sit','prod'], description: '环境')
string(name: 'PROJECT', defaultValue: 'xx_project', description: '项目')
}
triggers {
cron('H 9 * * *')
}
stages {
stage('Checkout') {
steps {
script {
// 使用echo 输出变量需用双引号,单引号会当成字符串
echo "${target}"
checkout([$class: 'GitSCM', branches: [[name: '$target']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CleanBeforeCheckout']], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '${credentialsId}', url: '$project_url']]])
echo 'Checkout'
// 此处路径地址需要和前文中conftest中配置的一致,发送消息的脚本需读取;workspace相当于你的项目根路径
result_json_path="${env.WORKSPACE}"+"/"+"reports/result.json"
echo "${result_json_path}"
}
}
}
stage('Set Build Name And Description'){
steps{
script{
currentBuild.displayName = "#${BUILD_NUMBER} *** ${PROJECT} ** ${ENV} * ${target}"
currentBuild.description = "本次构建信息:${BUILD_NUMBER} 项目名:${PROJECT} 环境:${ENV} 分支名:${target}"
}
}
}
stage('get_GIT_COMMIT_MSG') {
steps {
script {
env.GIT_COMMIT_MSG = sh (script: 'git log -1 --pretty=%B ${GIT_COMMIT}', returnStdout: true).trim()
env.COMMT= sh( returnStdout: true, script: 'git log --oneline -1 | awk \'{print \$1}\'')
echo "${env.GIT_COMMIT_MSG}"
}
}
} stage('get_BUILD_USER') {
steps {
script{
wrap([$class: 'BuildUser']) {
BUILD_USER = "${env.BUILD_USER}" }
if ("${BUILD_USER}"!="null"){
echo "${BUILD_USER}"
}else{
BUILD_USER = "定时器"
}
}
}
}
stage('执行全部项目检查脚本 ') {
steps {
// 执行测试脚本
sh '''
/usr/bin/python3 -m pytest tests --html=reports/report.html --self-contained-html
'''
}
}
}
post {
always {
//jenkins插件中的pytest-html报告插件,如果用allure的自行替换即可
publishHTML (target:[
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: './reports/',
reportFiles: 'report.html',
reportName: 'HTML Report',
reportTitles: ''
]
)
script{
// 执行前面的发送消息脚本,机器人id请自行替换。
sh """
cd /data/deploy/notify
/usr/local/python3/bin/python3 send_msg_2_lark_for_test.py '机器人id' '${currentBuild.currentResult}' \
'${PROJECT}' '${ENV}' '${target}' '${BUILD_USER}' '${currentBuild.durationString}' '${env.BUILD_URL}' '${GIT_URL}' '${env.COMMT}' '${env.GIT_COMMIT_MSG}' '${result_json_path}'
"""
}
}
}
}
执行job
Jenkins系列之pipeline语法介绍与案例的更多相关文章
- 7.Jenkins进阶之流水线pipeline语法入门学习(2)
目录一览: (2) Declarative Pipeline Syntax 2.1) Sections - 章节 2.2) Directives - 指令 2.3) Sequential Stages ...
- Jenkins pipeline:pipeline 语法详解
jenkins pipeline 总体介绍 pipeline 是一套运行于jenkins上的工作流框架,将原本独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂流程编排与可视化. ...
- Jenkins Pipeline 语法
Pipeline语法 先讲Declarative Pipeline,所有声明式管道都必须包含在pipeline块中: 123 pipeline { /* insert Declarative Pipe ...
- jenkins pipeline语法
目录 一.声明式 二.脚本式 基本 判断 异常处理 Steps node withEnv 一.声明式 声明式Pipeline必须包含在名为pipeline的语句块中,典型的声明式Pipeline语法如 ...
- 6.Jenkins进阶之流水线pipeline语法入门学习(1)
目录一览: 0x00 前言简述 Pipeline 介绍 Pipeline 基础知识 Pipeline 扩展共享库 BlueOcean 介绍 0x01 Pipeline Syntax (0) Groov ...
- devops-2:Jenkins的使用及Pipeline语法讲解
DevOps-Jenkins Jenkins简介 Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件项目可以进行持续 ...
- openresty开发系列13--lua基础语法2常用数据类型介绍
openresty开发系列13--lua基础语法2常用数据类型介绍 一)boolean(布尔)布尔类型,可选值 true/false: Lua 中 nil 和 false 为"假" ...
- jenkins创建multibranch pipeline
参考以下文章进行实践: https://jenkins.io/doc/pipeline/tour/hello-world/#what-is-a-jenkins-pipeline (看见一个介绍的还不错 ...
- 【03】emmet系列之CSS语法
[01]emmet系列之基础介绍 [02]emmet系列之HTML语法 [03]emmet系列之CSS语法 [04]emmet系列之编辑器 [05]emmet系列之各种缩写 单位: 有几个常用值别 ...
随机推荐
- js实现工具函数中groupBy数据分组
数据 this.tableData = [ {id: 1, name: '测试', number: 1, price: 0}, {id: 2, name: '测试', number: 1, price ...
- 简述伪共享和缓存一致性MESI
什么是伪共享 计算机系统中为了解决主内存与CPU运行速度的差距,在CPU与主内存之间添加了一级或者多级高速缓冲存储器(Cache),这个Cache一般是集成到CPU内部的,所以也叫 CPU Cache ...
- XCTF(Web_php_unserialize)
拿到题目,是个这, 我们来一波代码审计 1 <?php 2 class Demo { 3 private $file = 'index.php'; 4 public function __con ...
- Python实训day07pm【Selenium操作网页、爬取数据-下载歌曲】
练习1-爬取歌曲列表 任务:通过两个案例,练习使用Selenium操作网页.爬取数据.使用无头模式,爬取网易云的内容. ''' 任务:通过两个案例,练习使用Selenium操作网页.爬取数据. 使用无 ...
- 刚进公司,不懂GIt工作流的我瑟瑟发抖
前言 不懂git工作流,被辞退了! 之前在看到这句话的时候,我刚实习入职不久,瑟瑟发抖.好巧不巧,今天又看到了类似的文章讲git重要性的. 眼下,学校导师安排给我的课题组了一个新的工程项目,使用git ...
- 2021年SpringBoot面试题200道及答案
https://blog.csdn.net/yanpenglei/article/details/120822218 https://blog.csdn.net/ldb987/article/deta ...
- MySQL查询处理——逻辑查询处理和物理查询处理
对于查询处理,可将其分为逻辑查询处理和物理查询处理.逻辑查询处理表示执行查询应该产生什么样的结果,而物理查询代表MySQL数据库是如何得到结果的. 逻辑查询处理 MySQL真正的执行顺序如下: (8) ...
- elasticsearch源码分析及插件开发
ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口.Elasticsearch是用Java开发的,并作为Apach ...
- WebGPU图形编程(2):构建一个单色的三角形<学习引自徐博士教程>
非常兴奋,我坚持了下来,开始更新我的第二篇博客,还是关于WebGPU的,我在学习过程中,对这项技术非常感兴趣,即使它非常抽象,难以理解,因为我看到未来Web3D的发展,WebGPU会成为主流技术,学习 ...
- elasticsearch启动错误解决办法
1.max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] 解决: [r ...