在进一步讨论 Pipeline 两种语法模型的区别前先来了解下什么是 pipeline,为什么要到它。
Pipeline 是 Jenkins 中的一套自动化流程框架,代表一系列工作流或活动流,作用是允许将单个Jenkins节点或者多个节点的任务组合连接起来,从而实现单个任务难以完成的复杂构建工作。
它有以下优点:
Pipeline 包括声明式语法和脚本式语法
pipeline {
agent any //
stages {
stage('Build') {
steps {
//
}
}
stage('Test') {
steps {
//
}
}
stage('Deploy') {
steps {
//
}
}
}
}
node {
stage('Build') {
//
}
stage('Test') {
//
}
stage('Deploy') {
//
}
}
声明式 Pipeline 会在执行前就会校验 Pipeline 语法是否正确,而脚本式不会。
观察下面声明式例子,stage Test 里面的 ‘echo 1’ 有语法错误,echo 只可以接受字符串,尝试执行该 pipeline时,会立即报错
pipeline {
agent any
stages {
stage("Build") {
steps {
echo "Some code compilation here..."
}
}
stage("Test") {
steps {
echo "Some tests execution here..."
echo 1
}
}
}
}
同样脚本式的例子如下,会直到执行到 stage Test 才会报错
node {
stage("Build") {
echo "Some code compilation here..."
}
stage("Test") {
echo "Some tests execution here..."
echo 1
}
}
想象一下如果有多个 stage,前面的stage 执行都没有问题而最后一个stage 出问题,这将会浪费一定的时间
如果某个 stage 执行失败,修复后声明式 Pipeline 可以直接跳到该 stage 重新执行,而脚本式要从头来过。
观察下面两个图,声明式可以 restart stage,脚本式不能
option指令用于配置整个jenkins pipeline本身的选项,根据具体的选项不同,可以将其放在pipeline块或者stage块中。
虽然声明式pipeline 和脚本式 pipeline 都支持 options 选项,但声明式 options 和 pipeline 代码逻辑是分开的,而脚本式 options 和代码逻辑是嵌套在一起的,如果有多个options需要设置代码可读性差。观察下面例子:
声明式
pipeline {
agent any
options {
timestamps()
ansiColor("xterm")
}
stages {
stage("Build") {
options {
timeout(time: 1, unit: "MINUTES")
}
steps {
sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'
}
}
stage("Test") {
options {
timeout(time: 2, unit: "MINUTES")
}
steps {
sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'
}
}
}
}
脚本式
node {
timestamps {
ansiColor("xterm") {
stage("Build") {
timeout(time: 1, unit: "MINUTES") {
sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'
}
}
stage("Test") {
timeout(time: 2, unit: "MINUTES") {
sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'
}
}
}
}
}
观察下面声明式例子,添加条件当 env.FOO 等于 bar 时才执行 test stage
声明式
pipeline {
agent any
options {
timestamps()
ansiColor("xterm")
}
stages {
stage("Build") {
options {
timeout(time: 1, unit: "MINUTES")
}
steps {
sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'
}
}
stage("Test") {
when {
environment name: "FOO", value: "bar"
}
options {
timeout(time: 2, unit: "MINUTES")
}
steps {
sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'
}
}
}
}
脚本式
node {
timestamps {
ansiColor("xterm") {
stage("Build") {
timeout(time: 1, unit: "MINUTES") {
sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'
}
}
if (env.FOO == "bar") {
stage("Test") {
timeout(time: 2, unit: "MINUTES") {
sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'
}
}
}
}
}
}
从结果来看,声明式会正常跳过 Test stage,而脚本式需要if 判断才会添加 test stage,所以 test stage 根本没有被渲染出来,这会一定程序上造成困扰。
声明式支持 stages,steps 更细致的阶段性任务划分,脚本式只支持 stage, 编写逻辑时,声明式要在 script{} 标记里面写,脚本式不用,观察下面例子
声明式
pipeline {
agent {
// executes on an executor with the label 'some-label' or 'docker'
label "some-label || docker"
}
stages {
stage("foo") {
steps {
// variable assignment (other than environment variables) can only be done in a script block
// complex global variables (with properties or methods) can only be run in a script block
// env variables can also be set within a script block
script {
foo = docker.image('ubuntu')
env.bar = "${foo.imageName()}"
echo "foo: ${foo.imageName()}"
}
}
}
stage("bar") {
steps{
echo "bar: ${env.bar}"
echo "foo: ${foo.imageName()}"
}
}
}
}
脚本式
node {
git url: 'https://github.com/jfrogdev/project-examples.git'
// Get Artifactory server instance, defined in the Artifactory Plugin administration page.
def server = Artifactory.server "SERVER_ID"
// Read the upload spec and upload files to Artifactory.
def downloadSpec =
'''{
"files": [
{
"pattern": "libs-snapshot-local/*.zip",
"target": "dependencies/",
"props": "p1=v1;p2=v2"
}
]
}'''
def buildInfo1 = server.download spec: downloadSpec
// Read the upload spec which was downloaded from github.
def uploadSpec =
'''{
"files": [
{
"pattern": "resources/Kermit.*",
"target": "libs-snapshot-local",
"props": "p1=v1;p2=v2"
},
{
"pattern": "resources/Frogger.*",
"target": "libs-snapshot-local"
}
]
}'''
// Upload to Artifactory.
def buildInfo2 = server.upload spec: uploadSpec
// Merge the upload and download build-info objects.
buildInfo1.append buildInfo2
// Publish the build to Artifactory
server.publishBuildInfo buildInfo1
}