2.Jenkins进阶之流水线pipeline语法入门学习

2022-09-29 19:01:07 浏览数 (1)

[TOC]

0x00 前言简述

Pipeline 介绍

Q: 什么是 Pipeline?

答: Pipeline(流水线)是 Jenkins 2.0 的精髓它基于Groovy语言实现的一种DSL(领域特定语言),简而言之就是一套运行于Jenkins上的工作流框架,用于描述整条流水线是如何进行的。 它将原本独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂流程编排与可视化

Q: 什么是DSL?

答: DSL即 (Domain Specific Language) 领域专用语言,专门针对一个特定的问题领域,具有建模所需的语法和语义的语言。在与问题域相同的抽象层次对概念建模。 DSL 是 Jenkins 服务特有的一个语言,底层通过 Groovy 编程语言来实现。在使用过程中,可以很好的结合 Groovy。Jenkins Job DSL Plugin 提供了丰富的API,我们可以通过这些API实现对 Jenkinis 中View、Job 等管理。 Tips: Jenkins 内置了 Groovy 的引擎,我们可以通过 Groovy 编程语言在 DSL API 中添加逻辑编程。

Q: 什么是 Groovy 语言 答: Groovy 是 Apache 旗下的一门基于 JVM 平台的动态/敏捷编程语言,在语言的设计上它吸纳了 Python、Ruby 和 Smalltalk 语言的优秀特性,语法非常简练和优美,开发效率也非常高(编程语言的开发效率和性能是相互矛盾的,越高级的编程语言性能越差,因为意味着更多底层的封装,不过开发效率会更高,需结合使用场景做取舍)

Tips: PipeLine在Jenkins中的发展历史

  • 1.Jenkins 1.x 支持 Pipeline ,只不过是通过页面手动配置流水线。
  • 2.Jenkins 2.x 开始支持 pipeline as code ,可以通过代码来配置流水线了。

Q: 为什么要使用Pipeline?

1.Pipeline是Jenkins2.X的最核心的特性,帮助Jenkins实现从CI到CDAutoDevOps的转变; 2.Pipeline是一组插件它可以让Jenkins可以实现持续交付 Pipeline的落地和实施。 3.Pipeline提供了一组可扩展的工具,通过Pipeline Domain Specific Language(DSL) syntax可以达到 Pipeline as Code(Jenkinsfile存储在项目的源代码库)的目的。

Tips: 流水线的内容包括执行编译、打包、测试、输出测试报告等步骤。 Tips: 持续交付Pipeline (CD Pipeline)是将软件从版本控制阶段到交付给用户或客户的完整过程的自动化表现, 软件的每一次更改(提交到源代码管理系统)都要经过一个复杂的过程才能被发布。

Pipeline五大特性(优点)

  • 代码: Pipeline以代码的形式实现,通常被检入源代码控制,使团队能够编辑、审查和迭代其CD流程。
  • 可持续性:Jenklins重启或者中断后都不会影响Pipeline Job。
  • 停顿:Pipeline可以选择停止并等待构建人员的输入或批准,然后再继续Pipeline运行。
  • 多功能:Pipeline支持现实世界的复杂CD要求,包括fork/join子进程,循环和并行执行工作的能力
  • 可扩展:Pipeline插件支持其DSL的自定义扩展以及与其他插件集成的多个选项。

Tips: 它实现持续集成与部署、节省产品发布时间、优化部署策略、节省人力成本、以及自动化脚本复用等等;

Q: 怎样安装Pipeline插件?

答: 熟话说工欲善其事必先利其器,第一步当然需要安装Jenkins使用Pipeline所需的插件;

Jenkins pipeline 相关插件安装: 打开 Jenkins 找到 【系统管理】->【插件管理】->【可选插件】 然后在搜索框输入 Pipeline

代码语言:javascript复制
Pipeline 命令行接口 杂项 代理启动器和控制器 构建触发器  2.6 	2 年 3 月 ago 
# 说明:一套插件,让您编排自动化,简单或复杂。更多细节请参阅Jenkins的 Pipeline代码。

WeiyiGeek.Pipeline-Plug

Pipeline 基础知识

基础说明:

  • Pipeline 脚本是由 Groovy 语言结合 DSL 语言实现的。
  • Pipeline 支持两种语法:Declarative Pipeline (声明式 - 2.5 引入)Scripted Pipeline (脚本式)语法
  • Pipeline 也有两种创建方法:
    • 方式1、在 Jenkins 的 Web UI 界面中输入脚本;
    • 方式2、通过创建一个 Jenkinsfile 脚本文件(Groovy 语言结合 DSL 开发)放入项目源码库中 (推荐在 Jenkins 中直接从源代码控制(SCMD) 中直接载入 Jenkinsfile Pipeline)

语法差异: 描述: 最初创建 Jenkins Pipeline 时 Groovy 语言被选为基础。Jenkins长期以来一直提供嵌入式Groovy引擎,以为管理员和用户提供高级脚本功能。另外Jenkins Pipeline的实现者发现Groovy是构建现在称为”脚本 Pipelin” DSL的坚实基础。

由于它是功能齐全的编程环境,因此脚本化 Pipeline为Jenkins用户提供了极大的灵活性和可扩展性。Groovy学习曲线通常不是给定团队的所有成员所希望的,因此创建了声明式 Pipeline,以为编写Jenkins Pipeline提供更简单,更自以为是的语法。

两者基本上是下面的相同 Pipeline子系统。它们都是“ Pipeline作为代码”的持久实现。他们都可以使用内置在Pipeline中或由插件提供的步骤。两者都可以利用 共享库

但是它们的区别在于语法和灵活性。声明性限制使用更严格和预定义的结构为用户提供的功能,使其成为更简单的连续交付 Pipeline的理想选择。脚本化脚本提供的限制非常少,以至于对结构和语法的唯一限制往往是由Groovy本身定义的,而不是由任何特定于 Pipeline的系统定义的,因此,它成为高级用户和要求更复杂的用户的理想选择。顾名思义,声明性流水线鼓励使用声明性编程模型,而脚本 Pipeline 遵循更强制性的编程模型。

Q: 选择Declarative Pipeline还是Scripted Pipeline?

答: 最开始Pipeline plugin仅支持Scripted Pipeline一种脚本类型,而 Declarative Pipeline 为Pipeline plugin在2.5版本之后新增的一种脚本类型与原先的Scripted Pipeline一样都可以用来编写脚本。 由于在我们使用BlueOcean流水线UI插件后,Declarative Pipeline 与 BlueOcean 脚本编辑器是可以兼容使用,并且在eclarative Pipeline中,也是可以内嵌Scripted Pipeline代码的,所以通常建议使用 Declarative Pipeline (英 /dɪˈklærətɪv/)的方式进行编写

Declarative pipeline和Scripted pipeline的比较?

  • 1.共同点:
    • 声明式和脚本式流水线都是 DSL 语言,用来描述软件交付流水线的一部分。
    • 两者都能够使用pipeline内置的插件或者插件提供的step步骤部分。
    • 两者都可以利用共享库扩展。
  • 2.区别: 两者不同之处在于语法和灵活性;
    • Declarative pipeline 语法更严格 (例如必须以 pipeline 关键词打头),有固定的组织结构但更容易生成代码段,所以它成为用户更理想的选择。
    • Scripted pipeline 语法更加灵活,因为Groovy本身只能对结构和语法进行限制,对于更复杂的pipeline来说,用户可以根据自己的业务进行灵活的实现和扩展。

Tips: 脚本式语法的确灵活、可扩展,但是也意味着更复杂。而声明式语法供更简单、更结构化(more opinionated)的语法 (模块化的感觉)。

Pipeline 扩展共享库

描述: 由于流水线被组织中越来越多的项目所采用,常见的模式很可能会出现在多个项目之间共享流水线, 共享流水线有助于减少冗余并保持代码 “DRY(Don’t Repeat Yourself)”

Q: 如何定义共享库?

答: 我们将一些通用的代码或者代码包,封装定义为底层代码库,方便流水线创建。

特定的目录结构:

代码语言:javascript复制
(root)
 - src                     # Groovy source files
|    - org
|      - foo
|        - Bar.groovy      # for org.foo.Bar class
 - vars
|    - foo.groovy          # for global 'foo' variable
|    - foo.txt             # help for 'foo' variable
 - resources               # resource files (external libraries only)
|    - org
|        - foo
|            - bar.json    # static helper data for org.foo.Bar

目录结构解析:

  • 1、src 目录应该看起来像标准的 Java 源目录结构。当执行流水线时,该目录被添加到类路径下。
  • 2、vars 目录定义可从流水线访问的全局变量的脚本。 每个 .groovy 文件的基名应该是一个 Groovy (~ Java) 标识符, 通常是 驼峰命名法(camelCased)。 匹配 .txt, 如果存在可以包含文档, 通过系统的配置标记格式化从处理 (所以可能是 HTML, Markdown 等,虽然 txt 扩展是必需的)。这些目录中的 Groovy 源文件 在脚本化流水线中的 “CPS transformation” 一样。
  • 3、resources 目录允许从外部库中使用 libraryResource 步骤来加载有关的非 Groovy 文件。 目前,内部库不支持该特性。
  • 4、根目录下的其他目录被保留下来以便于将来的增强。

Q: 如何将将共享库设置为全局共享库?

描述: 在Jenkins 管理页面中的 “Configure System” 页面中的 “Global Pipeline Libraries” 中设置全局共享库。

Q: 如何使用封装的代码库

答: Jenkinsfile 文件中需要使用 @Library 注解,指定库的名字。另外关于代码库的动态加载、版本管理和检索方式等,请见官网。

Q: 如何编写自己的 Jenkins 共享库,共享库中的变量作用域?

答: 其他关于写库的访问步骤、定义全局变量 请见官网。

BlueOcean 介绍

Q: 什么是BlueOcean?

A: BlueOcean 重新考虑了 Jenkins 的用户体验而重新设置UI界面,从而更加直观的展现Pipeline各流程执行情况; BlueOcean由Jenkins Pipeline设计,但仍然兼容自由式工作,减少了团队成员的混乱,增加了清晰度。

Q: 为啥要使用BlueOcean?

连续交付(CD)Pipeline的复杂可视化,允许快速和直观地了解Pipeline的状态。 Pipeline编辑器通过引导用户直观和可视化的过程创建Pipeline,使创建Pipeline平易近人。 个性化,以适应团队每个成员的角色需求。 需要干预和/或出现问题时确定精度。BlueOcean显示了Pipeline需要注意的地方,便于异常处理和提高生产率。 用于分支和拉取请求的本地集成可以在GitHub和Bitbucket中与其他人进行代码协作时最大限度提高开发人员的生产力。

Q: 如何安装BlueOcean?

A: 同样是在插件中搜索 ”Blue Ocean“ 下载安装即可 Blue Ocean - 外部工具集成 用户界面 - BlueOcean Aggregator - 1.24.3 2 月 5 天 ago


0x01 Pipeline Syntax

(0) Groovy Basic Syntax

描述: 我们前面说过不管是声明式还是脚本式都是基于Groovy语言,所以学习 Groovy 基础知识是必须的。

1.虽然Groovy同时支持静态类型和动态类型,但是在定义变量时,在Groovy中我们习惯使用def关键字

代码语言:javascript复制
def x="abc"
def y=1

2.不像 Java语法语句,Groovy语句最后的分号不是必需的。

3.Groovy中的方法调用可以省略括号,比如System.out.println “Hello world”。

代码语言:javascript复制
System.out.println x
println t

4.支持单引号、双引号。双引号支持插值(变量),单引号不支持。

5.支持三引号。三引号分为三单引号和三双引号。它们都支持换行,区别在于只有三双引号支持插值(变量)。

代码语言:javascript复制
def var = """
This is Variable!
<br>
Test defiend
"""

6.支持函数。

代码语言:javascript复制
def getSecure(String Ticket_Token) {
  def token = "Ticket Token is "   Ticket_Token
  return token
}

println getSecure("weiyigeek")  //Ticket Token is weiyigeek

7.支持闭包。

代码语言:javascript复制
// # 闭包的定义方法:
def codeBlock = {print "hello world!"}
// codeBlock() //

// # 闭包的另类用法:
// 定义一个stage函数
def stage(String name, closue) {
  println name
  def closue() {
    println "闭包调用的 closue function!"
  }
}
stage("stage name",{println "closue"})

8.支持类定义和实例化。

代码语言:javascript复制
class Greet {
  def name
  Greet(who) { name = who[0].toUpperCase()   who[1..-1] }
  def salute() { println "Hello "   name   "!" }
}
 
g = new Greet('world')  // create object
g.salute()   // Hello World!

(1) Scripted Pipeline Syntax

描述: Scripted Pipeline 是基于 groovy 的一种 DSL 语言相比于 Declarative pipeline,它为jenkins用户提供了更巨大的灵活性和可扩展性。

Scripted Pipeline 基础结构说明:

  • Node:节点,一个 Node 就是一个 Jenkins 节点,Master 或者 Agent,是执行 Step 的具体运行环境,比如我们之前动态运行的 Jenkins Slave 就是一个 Node 节点
  • Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作,比如:Build、Test、Deploy,Stage 是一个逻辑分组的概念,可以跨多个 Node
  • Step:步骤,Step 是最基本的操作单元,可以是打印一句话,也可以是构建一个 Docker 镜像,由各类 Jenkins 插件提供,·比如命令:sh 'make',就相当于我们平时 shell 终端中执行 make 命令一样。 (注意:此处Step不是cripted Pipeline关键字而是代表一条执行语句)

Scripted Pipeline 语法示例:

代码语言:javascript复制
// Jenkinsfile (Scripted Pipeline)
// #结构1
node {
    // @变量定义
    def mvnHome
    // #结构2
    stage('Preparation') {
      // # 结构3 - 它就是 Step 基本操作单元
      echo "Scripted Pipeline"
    }
}

Tips : 注释(Comments)和Java一样,支持单行(使用//)、多行(/* */)和文档注释(使用/** */)

Hello-World 实践

Step 1.在Jenkins的WEB UI -> 新建任务 -> simple-pipeline-demo 任务名称 -> 选择流水线 -> 确定

Step 2.在 Dashboard -> simple-pipeline-demo -> 流水线 -> 可以选择pipeline script(或者直接从scm拉取Jenkinsfile)此处为了演示只是简单的了解 -> 应用保存

代码语言:javascript复制
# Scripted Pipeline 脚本式
node {
  stage('Clone') {
    echo "1.Clone Stage"
  }
  stage('Test') {
    echo "2.Test Stage"
  }
  stage('Build') {
    echo "3.Build Stage"
  }
  stage('Deploy') {
    echo "4. Deploy Stage"
  }
}

WeiyiGeek.Create PipeLine 流水线项目

Step 3.立即构建 -> 查看阶段视图 (或者利用blue-Ocean插件)进行更加直观的查看 -> 观察构建的日志信息

代码语言:javascript复制
Started by user admin
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/lib/jenkins/workspace/simple-pipeline-demo
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Clone)
[Pipeline] echo
1.Clone Stage
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Test)
[Pipeline] echo
2.Test Stage
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Build)
[Pipeline] echo
3.Build Stage
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Deploy)
[Pipeline] echo
4. Deploy Stage
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

WeiyiGeek.jenkins流水线执行结果与blue-Ocean

PS : 你可以选择使用BlueOcean或者jenkins原生的流水控制台展示两则并不冲突,但是需要注意Scripted pipeline不完全兼容BlueOcean;

变量名-Identifiers

描述: 标识符(Identifiers)也称变量名, 以字母、美元符号$或下划线_开始,不能以数字开始。 例如以下是可用的标识符:

代码语言:javascript复制
def name
def item3
def with_underscore
def $dollarStart

以下是不可用的标识符:

代码语言:javascript复制
def 3tier // 不能以数字开始
def a b // " "号是非法字符
def a#b // #号也不是可用的字符

Tips : 在点号后是可以使用关键字作为标识符时产生org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:错误;

代码语言:javascript复制
foo.as
foo.assert
foo.break
foo.case
foo.catch
字符串-String

描述: 在Groovy中字符串有两种类型,一种是Java原生的java.lang.String;另一种是groovy.lang.GString,又叫插值字符串(interpolated strings)。

(1) 单引号字符串(Single quoted string) 在Groovy中,使用单引号括住的字符串就是java.lang.String,不支持插值:

代码语言:javascript复制
def name = 'yjiyjgie'
println name.class // class java.lang.String

(2) 三单引号字符串(Triple single quoted string) 使用三单引号括住字符串支持多行,也是java.lang.String实例,在第一个’‘’起始处加一个反斜杠可以在新一行开始文本:

代码语言:javascript复制
def strippedFirstNewline = '''line one
line two
line three
'''

// 可以写成下面这种形式,可读性更好

def strippedFirstNewline = '''
line one
line two
line three
'''

(3) 双引号字符串(Double quoted string) 如果双引号括住的字符串中没有插值表达式(interpolated expression),那它就是java.lang.String;如是有插值表达式,那它就是groovy.lang.GString:

代码语言:javascript复制
def normalStr = "yjiyjige" // 这是一个java.lang.String

def interpolatedStr = "my name is ${normalStr}" // 这是一个groovy.lang.GString

(4) 字符串插值(String interpolation) 在Groovy所有的字符串字面量表示中,除了单引号字符串和三单引号字符串,其他形式都支持字符串插值。字符串插值也即将占位表达式中的结果最终替换到字符串相应的位置中:

代码语言:javascript复制
def name = 'Guillaume'          // a plain string
def greeting = "Hello ${name}" // name变量的值会被替换进去
assert greeting.toString()  == 'Hello Guillaume'

//当使用点号表达式时,可以只用$代替${}:
def person = [name: 'Guillaume', age: 36]
println "$person.name is $person.age years old"

补充说明:

代码语言:javascript复制
// 插值占位符中还支持闭包,而闭包的一个好处是惰性求值(lazy evaluation):
def number = 1
def eagerGString = "value == ${number}" // 普通形式
def lazyGString = "value == ${-> number}" // 这是一个闭包

println eagerGString // == "value == 1"
println lazyGString  // == "value == 1"

number = 2
println eagerGString // == "value == 1" // eagerGString已经被固定下来了
println lazyGString  // == "value == 2" // lazyGString的值会被重新计算

Tips: GString与String的hashCode是不一样的即使他们最终结果一样。所以在Map中不应该用GString去做元素的Key,而又使用普通的String去取值;

代码语言:javascript复制
// 当一个方法的需要一个java.lang.String变量,而我们传递的是一个groovy.lang.GString实例时,GString的toString方法会被自动调用,看起来像我们可以直接将一个GString赋值给一个String变量一样。

def key = "a"
def m = ["${key}": "letter ${key}"] // key类型是一个GString
assert m["a"] // == null // 用一个普通String类型的key去取值取代的值为null 而并非 letter a

Tips : 对于输出对象带有指定方法时如有需要拼接其它字符串需要以${对象.方法}进行包含;

代码语言:javascript复制
def number = 3.14
println "$number.toString()" // 这里会报异常,因为相当于"${number.toString}()"
println "${number.toString()} -- Other String、" // 这样就正常了
数字 - Numbers

描述: 当使用def指明整数字面量时,变量的类型会根据数字的大小自动调整:

代码语言:javascript复制
// 如果要强制指明一个数字的字面量类型,可以给字面量加上类型后缀:
BigInteger 使用G或g
Long 使用L或l
Integer 使用I或i
BigDecimal 使用G或g
Double 使用D或d
Float 使用F或f

def a = 1
assert a instanceof Integer

// Integer.MAX_VALUE
def b = 2147483647
assert b instanceof Integer


// Integer.MAX_VALUE   1
def c = 2147483648
assert c instanceof Long


// Long.MAX_VALUE
def d = 9223372036854775807
assert d instanceof Long

// Long.MAX_VALUE   1
def e = 9223372036854775808
assert e instanceof BigInteger

Tips : 为了精确地计算小数,在Groovy中使用def声明的小数是BigDecimal类型的:

代码语言:javascript复制
def decimal = 123.456
println decimal.getClass() // class java.math.BigDecimal
列表-List

描述:默认情况下Groovy的列表使用的是java.util.ArrayList,用中括号[]括住,使用逗号分隔:

代码语言:javascript复制
#  定义一个 list 隐式
def numbers = [1, 2, 3]
println numbers.getClass() # // class java.util.ArrayList

def arrayList = [1, 2, 3]  # // 默认类型
# assert arrayList instanceof java.util.ArrayList

# 如果要使用其它类型的列表(如:LinkedList)可以使用as操作符或显式分配给一个指定类型的变量:
def linkedList = [2, 3, 4] as LinkedList # // 使用as操作符
# assert linkedList instanceof java.util.LinkedList

LinkedList otherLinked = [3, 4, 5]         # // 显式指明类型
# assert otherLinked instanceof java.util.LinkedList

Groovy重载了列表的[]和<<操作符,可以通过List[index]访问指定位置元素,也可以通过List << element往列表末尾添加元素:

代码语言:javascript复制
// 定义一个list
def letters = ['a', 'b', 'c', 'd']
assert letters[0]  //  == 'a'
assert letters[1]  // == 'b'
assert letters[-1] // == 'd' 从后面访问 倒数第一个
assert letters[-2] // == 'c' // 从后面访问 倒数第二个

// 元素修改
letters[2] = 'C'    
assert letters[2]   //  == 'C'

letters << 'e' // 往最后面添加元素
assert letters[4] == 'e'
assert letters[-1] == 'e'
assert letters[1, 3] == ['b', 'd']      // 提取指定元素
assert letters[2..4] == ['C', 'd', 'e'] // 支持范围(ranges)操作

// 二维列表
def multi = [[0, 1], [2, 3]]
assert multi[1][0] == 2

常用方法

代码语言:javascript复制
def mylist = [1,2,3,4,5,"weiyigeek.top","devops","jenkins"]
 
// 通过索引获取列表元素
println(numList[5])  // weiyigeek.top

// 列表的长度
println(mylist.size())

// 计算列表中元素出现的次数
println(numList.count(4))

// 判断元素是否为空
println(mylist.isEmpty())

// 列表元素增删
//   操作符性能比较低,从而建议使用<<
def newlist = mylist.add("gitlab")
println(mylist   "jenkins")
println(mylist - "devops")
println(mylist << "java")

// 列表元素移除
println(mylist.remove("java")) // 移除指定元素,成功则返回True
println(mylist.removeAll())

// 整数取最值、求和
println(mylist.min())
println(mylist.max())
println(mylist.sum())

// 列表去重
println(mylist.unique())
 
// 列表反转
println(mylist.reverse())
 
// 列表排序
println(mylist.sort())
 
// 取交集、判断是否有交集
println(mylist.intersect([2,3]))
println(mylist.disjoint([2,3]))

// 判断列表是否包含元素
println(mylist.contains("devops"))
// 或者使用in关键字
def isContains = 'devops' in strList
println(isContains)

// 列表拼接插入到中间
println(mylist.join("geek"))

// 列表克隆
def list = ['a', 'b', 'c']
def newlist = list.clone()

// 合并嵌套列表
def mylist = [1,2,3,[4,5],5,"weiyigeek.top","devops","jenkins"]
println(mylist.flatten())

//扩展列表定义方式
String[] stus = ["Weiyi", "Geek","Top"]
def numList = [1,2,3,4,5,6,6] as int[]

// 将list集合通过调用collect()把list中的值*2构成一个新的集合,构成的新集合是[2,4,6]
def list = [1, 2, 3]
def newList = list.collect { it * 2 }
def newList = list*.multiply(2)

// 把元素重复的复制几次
def list = [1,2,3]
def newList = list*3
def newList = list.multiply(2)

// 使用findAll()找到所有匹配的元素
println(list.findAll{ it > 1 })  // [2,3]

// 判断集合中的值是否都小于5(与)
def isTrue = list.every { it < 5 }

// 判断集合中的值是否存在小于5(或)
def isTrue = list.any { it < 5 }

// 使用inject(index) 方法 集合中元素相加后再加index
def list = [1,2,4]
def num = list.inject(93){ count, item -> count   item }
println (num) // 100

// 使用findIndexOf()查找第一个元素匹配标准的索引
def strList =  ['a', 'b', 'c', 'd', 'e']
def index =  strList.findIndexOf {
  it in ['c', 'e', 'g']
}
println(index)  // 2

// 使用indexOf()查找集合某个值对应的下标值
def index =  strList.indexOf('c')  
println(index)  // 如果没有找到就返回-1否则返回下标值
字典 - Maps

描述: Groovy使用中括号[]来定义字典,元素需要包含key和value使用冒号分隔,元素与元素之间用逗号分隔:

代码语言:javascript复制
// key部分其实是字符串
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']
assert colors['red'] == '#FF0000' // 使用中括号访问
assert colors.green == '#00FF00' // 使用点表达式访问

// 两种方式
colors['pink'] = '#FF00FF'
colors.yellow = '#FFFF00'

assert colors.pink      // == '#FF00FF'
assert colors['yellow'] // == '#FFFF00'
assert colors instanceof java.util.LinkedHashMap // 默认使用LinkedHashMap类型

在上边的例子中,虽然没有明确的使用字符串’red‘、’green‘,但Groovy会自动把那些key转化为字符串。并且在默认情况下,初始化字典时key也不会去使用已经存在的变量:

代码语言:javascript复制
def keyVal = 'name'
def persons = [keyVal: 'Guillaume'] // 非常注意: 此处的key是字符串keyVal而不是name

assert !persons.containsKey('name')
assert persons.containsKey('keyVal')

// 如果要使用一个变量作为key,需要用括号括住:
def keyVal = 'name'
def persons = [(keyVal): 'Guillaume'] // 相当于[ 'name' : 'Guillaume' ]

assert persons.containsKey('name')
assert !persons.containsKey('keyVal')
条件语句 - Condition

if 语句 第一个决策语句是 if 语句。这种说法的一般形式是

代码语言:javascript复制
// # syntax
if(condition) {
  statement #1
  statement #2
  ...
}

例子:

代码语言:javascript复制
//@ 流程控制Groovy表达式如if/else条件语句
if (env.BRANCH_NAME == 'master') {
  echo 'I only execute on the master branch'
} else {
  echo 'I execute elsewhere'
}

For循环 for 语句用于遍历一组值。for 语句通常以以下方式使用。

代码语言:javascript复制
for(variable declaration;expression;Increment) {
  statement #1
  statement #2
  …
}

例子:

代码语言:javascript复制
// 常规
for(int i = 0;i<5;i  ) {
  println(i);
}
// List 循环迭代
def mylist = [1,2,3,"weiyigeek.top","devops","jenkins"]
mylist.each{ println(it) }
// 'it'是当前元素,而'i'是索引
mylist.eachWithIndex { it, i ->  println "$i: $it" }
mylist.eachWithIndex { count, count -> count   item }

Switch 语句 Swict 语句用于条件判断, 基础语法如下

代码语言:javascript复制
switch () {
  case '1':
    echo 1
    break
  case '2':
    echo 2
    break
  default:

}
异常 - Exception

描述:流程控制是Groovy的异常处理机制,在实际过程中建议同时使用try…catch..finally进行捕获异常;

代码语言:javascript复制
try {
      helloWorld()  // == Scripted Pipeline - Hello Wrold - STARTED - 1024!
      dir("place") {
          sh 'id'  // == uid=112(jenkins) gid=117(jenkins) groups=117(jenkins)
      }
  } catch (e) {
      // If there was an exception thrown, the build failed
      currentBuild.result = "FAILED"
      throw e
  } finally {
      println "success or failure, always send notifications"   // == Success or failure, always send notifications 
  }
函数 - Functions

描述:Groovy中的方法是使用返回类型或使用def关键字定义的, 方法可以接收任意数量的参数并定义参数时不必显式定义类型,可以添加修饰符如public,private和protected

Tips : 注意事项

  • 默认情况下如果未提供可见性修饰符则该方法为public。
  • 注意: 函数定义不能被包含在node{}块之中, 而函数调用是在 node { stage() { 函数名称} } 之中的;
  • 注意: 函数参数有定义默认值

简单示例:

代码语言:javascript复制
// 1.函数命令以及函数参数
def methodName(String param){
  println param  //Method code
}
// 2.支持函数重载,当参数个数不同时,函数名称可以同名
def methodName(String param = 'default1',int number = 1024) {
  println "Hello Wrold function - ${param} - ${age}!" 
  println param " " age
  // 如果没有显式地使用return关键字,则返回函数最后一行语句的运行结果。
  return "Hello Wrold function - ${param} - ${age}!"
}
// 3.Groovy支持不定长参数
def methodName(... param){
  println param[0]
}
// 4.函数可以赋值给其它函数,使用语法标记&将函数赋予新的函数。
def printHello=this.&methodName

node { 
  stage('函数调用') { 
    res00 = methodName("WeiyiGeek")
    res01 = methodName("WeiyiGeek",1024)
    res02 = printHello("WeiyiGeek")
  } 
}

闭包函数 描述: Groovy中闭包是这么定义的,可以用作函数参数和方法参数的代码块, 可以把这个代码块理解为一个函数指针。

Tips: 闭包可以访问外部的变量,记住一点方法是不能访问外部变量的。

代码语言:javascript复制
// # 闭包格式
def xxx = { params -> code }
def xxx={code} //或者

// 1.闭包可访问外部变量
def str="hello world"
def closure={
  println str
}
closure()  //hello world

// 2.闭包调用的两种方式
def closure = {
  param -> println param
}
closure("hello world")
closure "hello world"
closure.call("hell call")

// 3.闭包是有返回值的,默认最后一行语句就是该闭包的返回值,如果最后一行语句没有不输入任何类型,闭包将返回null
def noReturn={
    println "hello world"
}
println noReturn()
//hello world
//null

// 4.闭包可预定参数,如没有预定则有一个隐式的默认参数it,值得非常注意闭包中参数名称不能与闭包内或闭包外的参数名重名。
def closure={
  println "hello $it"
}
closure("admin")
 
def closure={
  param01,param02,param03->println param01 param02 param03
}
closure "hello","world","ok"
 
def closure={
  println it
}
closure "hello world"

// 5.闭包可以作为一个参数传递给另一个闭包,也可以在闭包中返回一个闭包。
def toTriple = { n -> n * 3 }
def runTwice = { a, c -> c(c(a)) }
println runTwice(5, toTriple)  // 15 * 3 =  45
 
def times = { x -> { y -> x * y } }
println times(3)(4)//12

// 6.当闭包作为闭包或方法的最后一个参数,可以将闭包从参数圆括号中提取出来接在最后。
def runTwice = { a, c -> c(c(a)) }
println runTwice(5, { it * 3 }) //45 usual syntax
//当多个闭包作为最后一个参数时
def runTwoClosures = { a, c1, c2 -> c1(c2(a)) }
assert runTwoClosures(5, { it * 3 }, { it * 4 }) == 60 //usual syntax

// 7.闭包接受参数的规则,会将参数列表中所有有键值关系的参数,作为一个map组装,传入闭包作为调用闭包的第一个参数。
def f= {m, i, j-> i   j   m.x   m.y }
println f(6, x:4, y:3, 7)//20


// 8.如果闭包的参数声明中没有list,那么传入参数可以设置为list,里面的参数将分别传入闭包参数。
def c = { a, b, c -> a   b   c }
def list = [1, 2, 3]
println c(list) // 6

参考地址: https://stackoverflow.com/questions/40870657/groovy-method-definition-not-expected-here

类和对象

Groovy类与Java类似,在字节码级都被编译成Java类,由于其在定义变量上面的灵活性,所以在新建一个Groovy类时还是有一些不同的,增加了许多灵活性。

1.由于Groovy是松散型语言,它并不强制你给属性、方法参数和返回值定义类型。如果没有指定类型,在字节码级别会被编译成Object,所以在定义类的属性时不用刻意加上权限修饰符,默认就是public的。

代码语言:javascript复制
class Book{
  def title
  String author
  private int price
  // 是否与Java的类差不多
  public Book(title){
    this.title=title
  }
  // 默认public
  boolean order(int isbn){
    true
  }
  def title(){
    "Booke Title"
  }
}

// 实例化对象
Book book=new Book("Hello Groovy")
book.order(1001)

// 使用说明
book.title   //获取属性
book.title() //访问方法

2.Groovy在编译完成后会自动帮助我们生成getter与setter方法,但是私有属性除外也就是说 price 属性我们不能使用getter与setter方法。

代码语言:javascript复制
// 例如 title 属性的 getter 与 setter 方法。
Book book=new Book("Hello Groovy")
println book.getTitle() // Hello Groovy
 
book.setTitle("New Groovy")
println book.getTitle() // New Groovy
println book.title      // New Groovy

3.在Groovy中类名和文件名并不需要严格的映射关系,我们知道在Java中主类名必须与文件同名,但是在Groovy中一个文件可以定义多个public类。 在Groovy中可以定义与任何类不相关的方法和语句,这些方法通常称为独立方法或者松方法。

代码语言:javascript复制
// Hello 类中有一个的公共静态 hello 方法
class Hello{
  public static String hello(){
      return "hello"
  }
}
class World{
  public static String world(){
    return "world"
  }
}
 
println Hello.hello() World.world()
 
def helloWorld(){
return "hello world"
}

上面一个文件名定义为Structure.groovy,在这个文件中包含了类的定义和独立方法声明,它编译之后会发生什么呢。

首先会生成一个与文件同名的class文件,所有的松语句都集中在run方法中,并且run方法被该类的main方法调用。 独立方法被编译成了类的静态方法,与Java相似每一个独立的类都会被编译成一个单独的class文件。 因此编译Structure.groovy文件最后会被编译成Hello.class、World.class和Structure.class

字符串处理

字符串分隔

split() :结果为字符串数组、保留空字符串、按照单词切割、支持使用使用正则

tokenize() :结果为List、但不会保留空字符串、按字符串切割、不支持使用使用正则

代码语言:javascript复制
def demo_string = "Hello WeiyiGeek"
out1 = demo_string.split("l")       # [He, , o WeiyiGeek]
out2 = demo_string.tokenize("l")    # [He, o WeiyiGeek]

out3 = demo_string.split('lo')      # [Hel, WeiyiGeek]
out4 = demo_string.tokenize('li')   # [Hel, We, y, Geek]

out5 = demo_string.split(/Wew{3}/) # [Hello , Geek]
语法总结

描述: 此次对 Scripted Pipeline 语法的使用进行一个简单的总结, 或许在后面的Declarative Pipeline中可以进行使用;

代码语言:javascript复制
node {
  // 变量定义
  def foo
  // 字符串
  def project="HelloWorld"
  // 单引号字符串(Single quoted string)不能解析变量
  def name='weiyigeek - ${project}'
  // 三单引号字符串(Triple single quoted string)不能解析变量
  def line='''
  Line one
  Line ${project}
  '''
  // 定义字典Maps
  def person = [name: 'WeiyiGeek', age: 96]  

  
  stage('Scripted Pipeline Syntax') {
    // (1) 变量声明
    foo="Identifiers"
    // (2) 变量调用
    echo "${foo} -- ${project}" // == Identifiers -- HelloWorld
    // (3) 字符串输出
    echo "my name is ${name}" // == my name is weiyigeek - ${project}
    // (4) 多行字符串输出
    echo "${line}" /* ==
    Line one
    Line ${project}
    */
    
    // (5) 变量对象属性输出
    echo "$person.name is $person.age years old" // == WeiyiGeek is 96 years old
    
    name = 'WeiyiGeek'         
    project = "my name is ${name}"
    def greeting = "Hello ${name}" // name变量的值会被替换进去
    
    // (6) 字符串格式化声明
    assert greeting.toString()
    println "${project.toString()}" //调用方法后直接输出 ==  my name is  WeiyiGeek
    
    // 闭包
    def number = 1
    def eagerGString = "value == ${number}"   // 普通形式
    def lazyGString = "value == ${-> number}" // 这是一个闭包
    println eagerGString // == value == 1
    println lazyGString  // 此处输出为空 (value == 1)
    number=1024
    echo "-----------------"
    println eagerGString // == value == 1
    println lazyGString  // 此处输出为空 (value == 1024)


    // (7) 变量类型输出
    println "变量name类型:" name.class ", n 变量project类型: " project.class ", n 变量greeting类型: " greeting.class 
    /* == 变量name类型:class java.lang.String, 
          变量project类型: class org.codehaus.groovy.runtime.GStringImpl, 
          变量greeting类型: class org.codehaus.groovy.runtime.GStringImpl */
    
    
    // (8) 数值类型与类型强转
    def decimal = 123.456
    println "${decimal.getClass()} -- ${decimal} " // ==  class java.math.BigDecimal -- 123.456
    //assert decimal instanceof BigDecimal
    
    // (9) 数组列表
    def arrayList = [1,2,3,"number"]
    println arrayList[0]   // == 1
    arrayList << '5' 
    echo "assert ${arrayList[0]} ---  ${arrayList[-2]} --- ${arrayList[-1]}"   // == assert 1 ---  number --- 5
    
    def multi = [[0, 1], [2, 3]]  // 二维数组
    echo "assert ${multi[1][0]}"  // == 2
    
    // (10) 字典Map
    def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']
    colors['pink'] = '#FF00FF'  
    colors.yellow = '#FFFF00'
    println "colors 类型 : "  colors.pink.class   ", assert colors.pink : "   colors.pink   ",assert colors['yellow'] : "   colors['yellow']
    /*       colors 类型 : class java.lang.String, assert colors.pink : #FF00FF,assert colors['yellow'] : #FFFF00     */
    
    // 如果要使用一个变量作为key(缺省为字符串),需要用括号括住:
    def keyVal = 'name'
    def persons = [(keyVal): 'WeiyiGeek'] // 相当于[ 'name' : 'WeiyiGeek' ]
    println "key is name: "   persons.containsKey('name')   " , Key not is KeyVal : "   !persons.containsKey('keyVal')
    /* key is name: true , Key is  KeyVal : true */
    
    // (11) 条件语句 - if - for - switch
    if ( keyVal == 'name' ) {
      echo "keyVal value is name"   // == keyVal value is name
    } else {
      echo "keyVal value is not name"
    }
    
    if (env.BRANCH_NAME == 'master') {
      echo 'I only execute on the master branch'
    } else {
      echo 'I execute elsewhere'
    }

    for (int i=0; i < 5; i  ) {
      println arrayList[i]  // 如果越界显示则 `null` // == 1,2,3,number,5
    }
    
    def option = "deploy"
    switch ("${option}") {
      case 'deploy':
          echo "deploy"   // == deploy
          break
      case 'rollback':
          echo "rollback"
          break
      default:
          echo "default"
    }
      
    // (12) 异常捕获
    try {
        helloWorld()  // == Scripted Pipeline - Hello Wrold - STARTED - 1024!
        dir("place") {
            sh 'id'  // == uid=112(jenkins) gid=117(jenkins) groups=117(jenkins)
        }
    } catch (e) {
        // If there was an exception thrown, the build failed
        currentBuild.result = "FAILED"
        throw e
    } finally {
        println "success or failure, always send notifications"   // == Success or failure, always send notifications 
    }  
      
    // (13)函数调用 
    helloWorld("weiyigeek")  /* Scripted Pipeline - Hello Wrold - weiyigeek - 1024! */
  }
}

// (13) 函数声明定义时 不能包括在node中
def helloWorld (String username = 'STARTED',int age = 1024) {
    println "Scripted Pipeline - Hello Wrold - ${username} - ${age}!" 
}

WeiyiGeek.Scripted Pipeline 示例


(2) Declarative Pipeline Syntax

描述: 前面说过Declarative Pipeline是 Jenkins Pipeline 的一个相对较新的补充, 它在Pipeline子系统之上提出了一种更为简化和有意义的语法。

Declarative Pipeline 中的基本语句和表达式遵循与Groovy语法相同的规则 ,但有以下例外:

  • 1.Pipeline的顶层必须是块,即所有有效的Declarative Pipeline必须包含在一个pipeline块内.
  • 2.没有分号作为语句分隔符,每个声明必须在自己的一行。
  • 3.块只能包含Sections, Directives, Steps或赋值语句。
  • 4.属性引用语句被视为无参方法调用。(例如: 输入被视为input)

参考流水线语法: http://jenkins.weiyigeek.top:8080/job/simple-pipeline-demo/pipeline-syntax

简单语法规范示例:

代码语言:javascript复制
// (1) 所有代码包裹在 Pipeline 块之中
pipeline {
  // (2) 定义任务在那台主机上运行可以是any、none(可以实现分布式的构建)等
  agent any
  // (3) 定义的环境变量比如PATH路径
  environment {
    hostname='Jenkins Pipeline'
  }
  // (4) 其类似一个大项目任务的集合主要包含所有stage子项目
  stages {
    // (5) 其类似一个项目中的单个任务主要来包含所有stage子任务
    stage ('clone') {
      // (6) 用来实现具体的执行的动作
      steps {
        // (7) 内置命令执行(信息打印)
        echo "git Clone Stage!"
      }
    }
    stage ('build') {
      steps {
        // (8) shell 命令执行 (Shell Script)
        sh "echo $hostname"
      }
    }
  }
  // (9) 消息通知
  post {
    always {
      echo "构建成功"
    }
  } 
}

语法格式说明: pipeline:代表整条流水线,包含整条流水线的逻辑。

  • agent 部分:指定流水线的执行位置(Jenkins agent)。流水线中的每个阶段都必须在某个地方(物理机、虚拟机或Docker容器)执行。
  • stage 部分:阶段,代表流水线的阶段。每个阶段都必须有名称。本例中,build就是此阶段的名称。
    • stages 部分:流水线中多个stage的容器。stages部分至少包含一个stage。
      • steps 部分:代表阶段中的一个或多个具体步骤(step)的容器。steps部分至少包含一个步骤。
    • post 部分:包含的是在整个pipeline或阶段完成后一些附加的步骤 (可选)
2.1) Sections - 章节

描述: 声明性 Pipeline中的节通常包含一个或多个指令或步骤(Steps)。

agent - 代理

描述: 指定整个Pipeline或特定阶段将在Jenkins环境中执行的位置,具体取决于该agent 部分的放置位置;

语法参数:

代码语言:javascript复制
必须: YES
参数:any / none / label / node / docker / dockerfile / kubernetes
  - 1.在任何可用的 agent 上执行Pipeline或stage
  - 2.在pipeline块的顶层应用时,不会为整个 Pipeline运行分配全局代理,并且每个stage部分都需要包含自己的agent部分。
  - 3.使用提供的标签在Jenkins环境中可用的代理上执行 Pipeline或阶段, 注意标签条件也可以使用。
  - 4.node使用与lable类似
  - 5.执行Pipeline或stage时会动态供应一个docker节点去接受Docker-based的Pipelines。
  - 6.使用从Dockerfile源存储库中包含的容器构建的容器执行 Pipeline或阶段,Jenkinsfile 必须从多分支 Pipeline或 SCM Pipeline加载。
  - 7.在Kubernetes集群上部署的Pod内执行 Pipeline或阶段,同样Jenkinsfile 必须从多分支 Pipeline或 SCM Pipeline加载,Pod模板在kubernetes {} 块内定义。
允许:在顶层pipeline块和每个stage块中。

语法示例:

代码语言:javascript复制
pipeline {
  agent any

  agent none

  agent { 
    label 'my-label1 && my-label2' 
  }

  agent { node { label 'labelName' } }  // 等同于 agent { label 'labelName' }

  // docker 还可以接受一个args直接传递给`docker run`调用以及一个 alwaysPull 选项
  // registryUrl和registryCredentialsId参数 有助于指定要使用的Docker注册表及其凭据
  agent {
    docker {
      image 'maven:3-alpine'
      label 'my-defined-label'
      args  '-v /tmp:/tmp'
      registryUrl 'https://myregistry.com/'
      registryCredentialsId 'myPredefinedCredentialsInJenkins'
    }
  }

  // dockerfile
  agent {
    // 等同于 to "docker build -f Dockerfile.build --build-arg version=1.0.2 ./build/
    dockerfile {
        filename 'Dockerfile.build'
        // 如果要Dockerfile在另一个目录中构建,请使用以下dir选项
        dir 'build'
        label 'my-defined-label'
        additionalBuildArgs  '--build-arg version=1.0.2'
        args '-v /tmp:/tmp'
        // 同样也接受registryUrl和registryCredentialsId参数
        registryUrl 'https://myregistry.com/'
        registryCredentialsId 'myPredefinedCredentialsInJenkins'
    }
  }

  // kubernetes: 例如如果要在其中装有Kaniko容器的容器
  agent {
    kubernetes {
      label podlabel
      yaml """
  kind: Pod
  metadata:
    name: jenkins-agent
  spec:
    containers:
    - name: kaniko
      image: gcr.io/kaniko-project/executor:debug
      imagePullPolicy: Always
      command:
      - /busybox/cat
      tty: true
      volumeMounts:
        - name: aws-secret
          mountPath: /root/.aws/
        - name: docker-registry-config
          mountPath: /kaniko/.docker
    restartPolicy: Never
    volumes:
      - name: aws-secret
        secret:
          secretName: aws-secret
      - name: docker-registry-config
        configMap:
          name: docker-registry-config
  """
  }
}

常用选项: 描述: 下面可以应用于两个或者多个agent实现的选项即label、customWorkspace、reuseNode;

  • 1.label (参数:字符串): 运行 Pipeline或单个 Pipeline的标签或标签条件stage。 【此选项对node,docker和有效对dockerfile必需 node。】
  • 2.customWorkspace (参数: 字符串) : 运行 Pipeline或个人 stage 这 agent 是这个自定义的工作空间内的应用,而不是默认的, 它可以是相对路径(在这种情况下自定义工作空间将位于节点上的工作空间根目录下),也可以是绝对路径。【此选项是有效的node,docker和dockerfile。】
  • 3.reuseNode(参数: 布尔值-false): 如果为true在同一工作空间中在 Pipeline顶级指定的节点上运行容器,而不是在整个新节点上运行
  • 4.args (参数: 字符串): 要传递给的运行时参数docker run,此选项对docker和有效dockerfile。
代码语言:javascript复制
// 示例1. Docker代理,声明性 Pipeline
pipeline {
  // V.在具有给定名称和标签(maven:3-alpine)的新创建容器中执行此 Pipeline中定义的所有步骤。
  agent { docker 'maven:3-alpine' } 
  stages {
      stage('Example Build') {
          steps {
              sh 'mvn -B clean verify'
          }
      }
  }
}

// 例子2.阶段级代理部分
pipeline {
  /* agent none在 Pipeline的顶层进行定义可确保 不会不必要地分配执行程序。使用agent none还会强制每个stage部分包含其自己的agent部分。 */
  agent none 
  stages {
    stage('Example Build') {
      /* 使用此映像在新创建的容器中执行此阶段中的步骤。*/
      agent { docker 'maven:3-alpine' } 
      steps {
          echo 'Hello, Maven'
          sh 'mvn --version'
      }
    }
    stage('Example Test') {
      /* 使用与上一阶段不同的图像在新创建的容器中执行此阶段中的步骤。 */
      agent { docker 'openjdk:8-jre' } 
      steps {
          echo 'Hello, JDK'
          sh 'java -version'
      }
    }
  }
}
stages - 阶段

描述: Stages 是 Pipeline描述的大部分“工作”所在的位置, 该部分包含一个或多个阶段指令的序列。对于连续交付过程的每个离散部分,建议stages至少包含一个阶段指令,例如Build,Test和Deploy。

位置&参数:

代码语言:javascript复制
必须: YES
参数:NONE
允许:pipeline块内只有一次

例子.阶段声明性 Pipeline

代码语言:javascript复制
pipeline {
    agent any
    // stages部分将典型地遵循指令,例如agent, options等
    stages { 
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}

Tips : 该部分必须在pipeline块内的顶层定义,但stage级使用是可选的。 Tips : 顶级代理和阶段代理之间的细微差别,而在options应用指令时会有所不同。

steps - 步骤

描述: 该阶段包含在给定指令中执行的一系列一个或多个步骤 stage 之中

位置&参数:

代码语言:javascript复制
必须: YES
参数:None
允许:每个Stage块之中
script - 脚本

描述: 前面我们说过我们可在Declarative Pipeline 中 采用script指令来执行Scripted Pipeline中的一些脚本;

例子.单步式声明式 && Script Block in Declarative Pipeline

代码语言:javascript复制
pipeline {
    agent any
    stages {
        stage('Example') {
          // 步骤部分必须包含一个或多个步骤。
          steps { 
              echo 'Hello World'
              // 执行 Scripted Pipeline (实际上就是直接执行并采用Groovy原生语法)
              script {
                def browsers = ['chrome', 'firefox']
                for (int i = 0; i < browsers.size();   i) {
                    echo "Testing the ${browsers[i]} browser"
                }
              }
          }
        }
    }
}
sh - 命令执行

描述: pipeline中获取shell命令的输出以及状态,注意其必须在steps 块以及 script 块之中

(0) 最简单的方式最简单的方式

代码语言:javascript复制
sh '<shell command>'

(1) 获取标准输出

代码语言:javascript复制
//第一种
result = sh returnStdout: true ,script: "<shell command>"
result = result.trim()
//第二种
result = sh(script: "<shell command>", returnStdout: true).trim()
//第三种
sh "<shell command> > commandResult"
result = readFile('commandResult').trim()

(2) 获取执行状态

代码语言:javascript复制
//第一种
result = sh returnStatus: true ,script: "<shell command>"
result = result.trim()

//第二种
result = sh(script: "<shell command>", returnStatus: true).trim()

//第三种
sh '<shell command>; echo $? > status'
def r = readFile('status').trim() //值得学习

//第四种
sh label: 'release', returnStdout: true, script:"""
sudo apk add jq && 
if ( $(curl -s --header 'PRIVATE-TOKEN: ${private_token}' ${GITLAB_REL} | jq .[].tag_name | grep -c '${params.RELEASE_VERSION}') != 0 );then echo -n 1 > result.txt;else echo -n 0 > result.txt;fi
"""
def r = readFile('result.txt').trim() //值得学习

// 注意使用'但引号也可以解析变量
sh(returnStatus: true, script: "sudo apk add jq && curl -s --header 'PRIVATE-TOKEN: ${private_token}' ${GITLAB_REL} | jq .[].tag_name | grep -c '${params.RELEASE_VERSION}'")

实际案例:

代码语言:javascript复制
// # (1) 命令拼接
sh ' $SonarScannerHome/bin/sonar-scanner '  
    '-Dsonar.sources=src/main '  
    '-Dsonar.projectKey="test" '  
    '-Dsonar.projectName="test" '
  
// # (2) 值传递到参数
stage ("测试") {
  steps {
    timeout(time: 1, unit: 'MINUTES') {
        script {
          def RELEASE=sh returnStdout: true, script: 'git tag -l --column'   // # git show --oneline --ignore-all-space --text | head -n 1
          env.DEPLOY_ENV = input message: "选择部署的环境的版本", ok: 'deploy',
              parameters: [string(name: 'DEPLOY_ENV',defaultValue: "${RELEASE}",description: "可选项选择的版本 ${RELEASE}")]
         }
      }
  }
}

// # (3) 多行命令执行(值得注意在sh中得 $符号 需要采用 $ 进行转义)
stage ("测试") {
  steps {
      timeout(time: 1, unit: 'MINUTES') {
      script {
          def RELEASE=sh returnStdout: true, script: 'git tag -l --column'
          def RELE=sh returnStdout: true, script: """
          git tag -l --column | tr -d ' '  > tag ;
          export a="$(sed "s#v#,'v#g" tag)'";
          echo [${a#,*}];
          """
          // env.DEPLOY_ENV = input message: "选择部署的环境的版本1", ok: 'deploy',
          //     parameters: [string(name: 'DEPLOY_ENV',defaultValue: "${RELEASE}",description: "可选项选择的版本 ${RELEASE}")]
          // }
          println RELE // ['v1.1,'v1.10,'v1.11,'v1.2,'v1.3,'v1.6,'v1.7,'v1.8,'v1.9']
          // 选择部署的环境的版本 ['v1.1,'v1.10,'v1.11,'v1.2,'v1.3,'v1.6,'v1.7,'v1.8,'v1.9']
          env.DEPLOY_RELEASE = input message: "选择部署的环境的版本 ${RELE}", ok: 'deploy',   
          parameters: [choice(name: 'PREJECT_OPERATION', choices: ["ss","s"], description: 'Message: 选择项目版本? ${RELE}')]
    }
  }
}

WeiyiGeek.script块&sh指令联合使用

Tips : 注意传递变量得生存周期以及范围,在pipeline全局中则全局有效,而stage块中则该块中有效,其他stage引用则报No such property: 变量名称 for class: groovy.lang.Binding错误

post - 发布

描述: 本post部分定义了在 Pipeline或阶段的运行完成后运行的一个或多个其他步骤(取决于该post部分在 Pipeline中的位置),即定义Pipeline或stage运行结束时的操作, 通常将清理工作空间以及构建状态的消息通知(Email/钉钉、企业微信或者其他WebHook); post可以支持任何以下的后置条件块:always, changed,fixed,regression,aborted,failure,success, unstable,unsuccessful,和cleanup, 条件块允许根据 Pipeline或阶段的完成状态在每个条件内执行步骤。

位置&参数:

代码语言:javascript复制
必须: NO

参数:`always,changed,fixed,regression,aborted,failure,success, unstable,unsuccessful,和cleanup`
  - always :post不管 Pipeline运行或阶段运行的完成状态如何,都运行本节中的步骤。
  - changed :仅post当当前 Pipeline或阶段的运行与之前的运行具有不同的完成状态时,才运行步骤。
  - fixed :仅post在当前 Pipeline或阶段的运行成功并且前一运行失败或不稳定的情况下运行步骤。
  - regression :仅post当当前 Pipeline或阶段的运行状态为失败,不稳定或中止并且上一次运行成功时,才运行步骤。
  - aborted :仅post在当前 Pipeline或阶段的运行状态为“中止”时才运行步骤,通常是由于手动中止了 Pipeline。通常在网络用户界面中用灰色表示。
  - failure :仅post当当前 Pipeline或阶段的运行具有“失败”状态时才运行这些步骤,通常在Web UI中用红色表示。
  - success :仅post当当前 Pipeline或阶段的运行具有“成功”状态时才运行步骤,通常在Web UI中用蓝色或绿色表示。
  - unstable :仅post在当前 Pipeline或阶段的运行状态为“不稳定”(通常由测试失败,代码冲突等引起)的情况下,才运行步骤。通常在Web UI中以黄色表示。
  - unsuccessful :仅post当当前 Pipeline或阶段的运行状态不是“成功”时才运行步骤。这通常根据前面提到的状态在Web UI中表示。
  - cleanup : 在评估post所有其他条件之后post,无论 Pipeline或阶段的状态如何,都在此条件下运行步骤。

允许位置:在顶层pipeline块和每个stage块中。

示例.Post 部分示例:

代码语言:javascript复制
pipeline {
  agent any
  stages {
    stage('Example') {
      steps {
          echo 'Hello World'
      }
    }
  }
  post { 
    always { 
      echo 'I will always say Hello again!'
    }
  }
}

2.2) Directives - 指令

描述: 显然基本结构满足不了现实多变的需求。所以Jenkins pipeline通过各种指令(directive)来丰富自己。指令可以被理解为对Jenkins pipeline基本结构的补充。

Jenkins pipeline支持的指令有:

  • environment:用于设置环境变量,可定义在stage或pipeline部分。
  • tools:可定义在pipeline或stage部分。它会自动下载并安装我们指定的工具,并将其加入PATH变量中。
  • input:定义在stage部分,会暂停pipeline,提示你输入内容。
  • options:用于配置Jenkins pipeline本身的选项,比如options {retry(3)}指当pipeline失败时再重试2次。options指令可定义在stage或pipeline部分。
  • parallel:并行执行多个step。在pipeline插件1.2版本后,parallel开始支持对多个阶段进行并行执行。
  • parameters:与input不同,parameters是执行pipeline前传入的一些参数。
  • triggers:用于定义执行pipeline的触发器。
  • when:当满足when定义的条件时,阶段才执行。

Tips: 在使用指令时需要注意的是每个指令都有自己的“作用域”。如果指令使用的位置不正确Jenkins将会报错。

environment - 环境

描述: 该指定了一系列键值对,这些键值对将被定义为所有步骤或特定于阶段的步骤的环境变量,具体取决于该environment指令在 Pipeline中的位置。

位置&参数:

代码语言:javascript复制
必须: No
参数:Yes
允许:在pipeline块内,或在stage指令内。

Tips : 非常注意该块中的变量将写入到Linux环境变量之中作为全局变量,在shell可通过变量名访问,而在script pipeline脚本中通过env.变量名称访问.

支持的凭证类型:Supported Credentials Type

  • Secret Text :设置为加密文本字符串内容
  • Secret File : 设置为临时创建的文件文件的位置, 并自动定义变量存储该文件内容。
  • Username and password : 将设置为username:password并且两个其他环境变量将自动定义为MYVARNAME_USR 和MYVARNAME_PSW
  • SSH with Private Key : 设置为临时创建的SSH密钥文件的位置,并且可能会自动定义两个其他环境变量:MYVARNAME_USRMYVARNAME_PSW(保留密码)。

示例1:秘密文本凭证,声明性 Pipeline

代码语言:javascript复制
pipeline {
  agent any
  // (1) 由于在pipeline下一层,则使用的指令将应用于 Pipeline中的所有步骤。
  environment { 
    CC = 'clang'
  }
  stages {
    stage('Example') {
      // (2) 在 stage 中定义的 environment指令只会将给定的环境变量应用于Example内的步骤。
        environment { 
          // 在environment块中credentials('凭据名称')定义的帮助程序方法通过其在Jenkins环境中的标识符来访问预定义的凭据
          AN_ACCESS_KEY = credentials('my-predefined-secret-text') 
        }
        steps {
          sh 'printenv'
          echo "${env.AN_ACCESS_KEY}"
          echo "${env.CC}"
        }
    }
  }
}

示例2.用户名和密码凭证

代码语言:javascript复制
pipeline {
  agent any
  stages {
    stage('Example Username/Password') {
      environment {
        // 变量 = 将用户密码凭证赋予变量
        SERVICE_CREDS = credentials('my-predefined-username-password')
      }
      steps {
        // 注意点: defined: MYVARNAME_USR and MYVARNAME_PSW respectively.
        sh 'echo "Service user is $SERVICE_CREDS_USR"'
        sh 'echo "Service password is $SERVICE_CREDS_PSW"'
        sh 'curl -u $SERVICE_CREDS https://myservice.example.com'
      }
    }
    stage('Example SSH Username with private key') {
      environment {
        // 变量 = 将 `ssh private` 公密钥进行赋予变量
        SSH_CREDS = credentials('my-predefined-ssh-creds')
      }
      steps {
        // 注意点: defined: MYVARNAME_USR and MYVARNAME_PSW (holding the passphrase).
        sh 'echo "SSH private key is located at $SSH_CREDS"'
        sh 'echo "SSH user is $SSH_CREDS_USR"'
        sh 'echo "SSH passphrase is $SSH_CREDS_PSW"'

        // 调用内置变量 (如果变量不存在则输出null) - 值得学习注意。
        echo "${JOB_NAME}"
        echo env.'JOB_NAME'
        println(env.'JOB_NAME')  

        // 自定义全局变量方式(写入文件中再读取)
        script {
          def projectProduct = sh returnStdout: true, script: "find ${APP_NAME}"
          if ( projectProduct != '' ){
            echo "${projectProduct}"
            writeFile file: 'abc.sh', text: "${projectProduct}"
            change_id = readFile 'abc.sh'
            print(change_id)
          } else {
            error "[-Error] : projectProduct 不能为空!"
          }
        }
        
      }
    }
  }
}

Tips : 该指令支持特殊的帮助程序方法credentials(),该方法可用于在Jenkins环境中通过其标识符访问预定义的凭据。

Tips : 如有不支持的凭据类型导致 Pipeline失败,并显示以下消息:org.jenkinsci.plugins.credentialsbinding.impl.CredentialNotFoundException: No suitable binding handler could be found for type <unsupportedType>.

options - 选项

描述: options 指令 允许在 Pipeline 本身内配置 Pipeline 专用选项, 例如 buildDiscarder 它们也可能由插件提供;

位置&参数:

代码语言:javascript复制
必须: No
参数:None
允许:pipeline块内只有一次。

可用选项:

1.buildDiscarder : 保存最近历史构建记录的数量。设置此选项后会自动清理pipeline 的构建历史。

代码语言:javascript复制
options { buildDiscarder(logRotator(numToKeepStr: '1')) }

2.disableConcurrentBuilds : 禁止并发执行 Pipeline 对于防止同时访问共享资源等很有用

代码语言:javascript复制
options { disableConcurrentBuilds() }

3.checkoutToSubdirectory : Jenkins从版本控制库拉取源码时,默认检出到工作空间的根目录中,此选项可以指定检出到工作空间的子目录中。

代码语言:javascript复制
options { checkoutToSubdirectory('foo') }

4.newContainerPerStage : 当agent为docker或dockerfile时,指定在同一个Jenkins节点上,每个stage都分别运行在一个新的容器中,而不是所有stage都运行在同一个容器中。

5.disableResume : 如果控制器重新启动则不允许 Pipeline恢复

代码语言:javascript复制
options { disableResume() }

6.overrideIndexTriggers : 允许重写分支索引触发器的默认处理,如果在多分支或组织标签处禁用了分支索引触发器。

代码语言:javascript复制
# 仅为该作业启用分支索引触发器
options { overrideIndexTriggers(true) } 

# 仅为此作业禁用分支索引触发器
options { overrideIndexTriggers(false) }

7.preserveStashes: 保留已完成构建中的隐藏项,以用于阶段重新启动。

代码语言:javascript复制
# 保存构建
options { preserveStashes() } 

# 保存最近5次构建
options { preserveStashes(buildCount: 5) }

8.quietPeriod:设置 Pipeline的静默时间段(以秒为单位),以覆盖全局默认值

代码语言:javascript复制
options { quietPeriod(30) }

9.retry:如果失败重试整个 Pipeline指定次数。该次数是指总次数包括第1次失败。

代码语言:javascript复制
options { retry(3) }

10.skipDefaultCheckout : 默认跳过来自源代码控制的代码(代理指令)。

代码语言:javascript复制
options { skipDefaultCheckout() }

11.skipStagesAfterUnstable : 一旦构建状态变得不稳定就跳过各个阶段;

代码语言:javascript复制
options { skipStagesAfterUnstable() }

12.timestamps : 预定义由Pipeline生成的所有控制台输出时间

代码语言:javascript复制
options { timestamps() }

13.parallelsAlwaysFailFast :将 Pipeline中所有后续并行阶段的failfast设置为true。

代码语言:javascript复制
options { parallelsAlwaysFailFast() }

14.timeout(常用) : 设置 Pipeline运行的超时时间在此之后Jenkins 应中止 Pipeline(运行的超时时间)。

代码语言:javascript复制
# 操守时间一个小时(HOURS/Minute)
options { timeout(time: 1, unit: 'HOURS') }

# Global Timeout, Declarative Pipeline
pipeline {
    agent any
    options {
      // 将全局执行超时指定为一小时,然后Jenkins将中止 Pipeline运行。
      timeout(time: 1, unit: 'HOURS') 
    }
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}

Tips: 在 stage 块中支持的 options 要少于 pipeline 块中,只能采用skipDefaultCheckout,timeout,retry,timestamps 等选项;

例如:

代码语言:javascript复制
pipeline {
    agent any
    stages {
        stage('Example') {
            // 关键单
            options {
                timeout(time: 1, unit: 'HOURS') 
            }
            steps {
                echo 'Hello World'
            }
        }
    }
}
parameters - 参数

描述: 该指令提供了一个用户在触发 Pipeline时应该提供的参数列表。这些用户指定参数的值通过params对象提供给 Pipeline步骤,请参阅参数,声明式 Pipeline的具体用法。

目前可用参数有string , text, booleanParam, choice, password等参数,其他高级参数化类型还需等待社区支持。

位置&参数:

代码语言:javascript复制
必须: No
参数: None
允许: 在`Pipeline`块内仅一次。

Tips : 非常注意全局参数, 在shell可通过变量名访问,而在script pipeline脚本中通过params.参数名称访问.

示例: Parameters, Declarative Pipeline

代码语言:javascript复制
Parameters, Declarative Pipeline
pipeline {
    agent any

    // Jenkins -> 原生 Build With Parameters 支持
    parameters {
      gitParameter name: 'RELEASE_VERSION', 
                      type: 'PT_BRANCH_TAG',
                      branchFilter: 'origin/(.*)',
                      defaultValue: 'master',
                      selectedValue: 'DEFAULT',
                      sortMode: 'DESCENDING_SMART',
            description: 'Message: 请选择部署的Tags版本?'
      choice(name: 'SONARQUBE', choices: ['False','True'], description: 'Message: 是否进行代码质量检测?')				 
 
      //  Jenkins -> 原生 Build With Parameters 支持 和 BlueOcean 都支持
      string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')

      text(name: 'BIOGRAPHY', defaultValue: '', description: 'Enter some information about the person')

      booleanParam(name: 'TOGGLE', defaultValue: true, description: 'Toggle this value')

      choice(name: 'CHOICE', choices: ['One', 'Two', 'Three'], description: 'Pick something')

      password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'Enter a password')
    }
    stages {
        stage('Example') {
            steps {
                // 调用
                echo "Hello ${params.PERSON}"

                echo "Biography: ${params.BIOGRAPHY}"

                echo "Toggle: ${params.TOGGLE}"

                echo "Choice: ${params.CHOICE}"

                echo "Password: ${params.PASSWORD}"
            }
        }
    }
}

WeiyiGeek.parameters

triggers - 触发器

描述: 该指令定义了Pipeline自动化触发的方式,对于与GitHub或BitBucket等源集成的 Pipeline可能不需要触发器,因为基于webhook的集成可能已经存在了。

位置&参数:

代码语言:javascript复制
必须: No
参数: None
允许: 在Pipeline块内

Tips : 当前可用的触发器是cron、pollSCM和upstream

1.cron : 以Linux中Cron风格的字符串,以定义应该重新触发 Pipeline的定期间隔

代码语言:javascript复制
triggers { cron('H */4 * * 1-5') }

2.pollSCM : 接受cron样式的字符串以定义Jenkins应检查新源更改的定期间隔。如果存在新的更改则将重新触发 Pipeline。

代码语言:javascript复制
triggers { pollSCM('H */4 * * 1-5') }
  • Tips : pollSCM触发器仅在Jenkins 2.22或更高版本中可用。

3.upstream : 接受以逗号分隔的作业字符串和阈值。当字符串中的任何作业以最小阈值结束时 Pipeline将被重新触发

代码语言:javascript复制
triggers { upstream(upstreamProjects: 'job1,job2', threshold: hudson.model.Result.SUCCESS) }

cron语法 描述: Jenkins cron语法遵循cron实用程序的语法(略有不同)。具体来说每行包含5个由TAB或空格分隔的字段:

代码语言:javascript复制
* * * * * 
MINUTE - Minutes within the hour (0–59)

HOUR   - The hour of the day (0–23)

DAY	   - The day of the month (1–31)

MONTH  - The month (1–12)

WEEK   - The day of the week (0–7) where 0 and 7 are Sunday.

如果要为一个字段指定多个值,可以使用以下操作符。按照优先顺序,

代码语言:javascript复制
*   specifies all valid values  (指定所有有效值)

M-N specifies a range of values (指定值范围)

M-N/X or */X steps by intervals of X through the specified range or whole valid range (在指定范围或整个有效范围内按X的间隔步进)

A,B,…​,Z enumerates multiple values (枚举多个值)

Tips : 在 Cron 中使用 H 字符为了使定期计划的任务在系统上产生均匀的负载,H符号可以被认为是在一定范围内的随机值。 例如使用0 0 * * *一打日常工作将导致午夜时分大幅增加。相反使用H H * * *仍会每天执行一次每个作业,但不是同时执行所有作业,更好地使用有限的资源

Tips : 此外@yearly,@annually,@monthly, @weekly,@daily,@midnight,并且@hourly也支持方便的别名。这些使用哈希系统进行自动平衡。 例如 @hourly与相同,H 并且可能表示该小时中的任何时间, @midnight实际上是指在12:00 AM和2:59 AM之间的某个时间。

示例.Triggers, Declarative Pipeline:

代码语言:javascript复制
// (1) Jenkins cron语法示例
// 每十五分钟执行触发
triggers{ cron('H/15 * * * *') }

// 每小时(1~30分钟)内每十分钟一次
triggers{ cron('H(0-29)/10 * * * *') }

// 每隔一个小时的45分钟,每两小时一次,从上午9:45开始,到每个工作日的下午3:45结束。
triggers{ cron('45 9-16/2 * * 1-5') }

// 每个工作日的上午9点至下午5点之间每两小时一次
triggers{ cron('H H(9-16)/2 * * 1-5') }

// 每月12月1日和15日每天一次
triggers{ cron('H H 1,15 1-11 *') }

//(2) Declarative Pipeline
pipeline {
    agent any
    triggers {
      cron('H */4 * * 1-5')
    }
    stages {
        stage('Example') {
            steps {
                echo 'Hello World'
            }
        }
    }
}
stage - 单阶段

描述: 该 stage 指令位于stages中并且应包含Step 节,可选 agent 节或其他特定于阶段的指令, 实际上管道完成的所有实际工作都将包含在一个或多个stage指令中。

位置&参数:

代码语言:javascript复制
必须: YES
参数: 步骤名称(字符串、必填)
允许: 在 Pipeline块 -> stages部分 内

示例.stage , Declarative Pipeline

代码语言:javascript复制
// Declarative 
pipeline {
  agent any
  stages {
    stage('Example') {
      steps {
        echo 'Hello World'
      }
    }
  }
}
Tools - 工具

描述: 定义自定义安装的Tools工具路径,并放置环境变量到PATH。如果agent none 这将被忽略 Supported Tools(Global Tool Configuration) : maven / jdk / gradle / Sonarqube

位置&参数:

代码语言:javascript复制
必须: No
参数: None
允许: 在 Pipeline 块 或者 stage 部分 内

示例.Tools ,声明性管道

代码语言:javascript复制
pipeline {
    agent any
    tools {
      // 工具名称必须在Jenkins中的 Manage Jenkins → Global Tool Configuration 下预先配置。
      maven 'apache-maven-3.0.1' 
    }
    stages {
      stage('Example') {
        steps {
          sh 'mvn --version'
          script {
            // 无指定名称时可用采用tool指定设置的全局工具名称,这里这个tool是直接根据名称,获取自动安装的插件的路径
            def scannerHome = tool 'sonarqubescanner'
          }
          withSonarQubeEnv('SonarQube') {
            sh "${scannerHome}/bin/sonar-scanner -Dsonar.projectKey=YourProjectKey -Dsonar.sources=."
          }
        }
      }
    }
}
Input - 输入

描述: input指令允许您使用输入步骤提示输入。在应用了任何选项之后,在进入该阶段的代理块或评估该阶段的when条件之前,该阶段将暂停。如果输入被批准,该阶段将继续。作为输入提交的一部分提供的任何参数都将在该阶段的其余部分的环境中可用。

位置&参数:

代码语言:javascript复制
必须: No
参数: message 、id 、ok、submitter、submitterParameter、parameters
允许: 在 Pipeline块 -> stages 块内 (注意不是在stage中)

配置选项

  • message : 必须的前台输入时提示用户的信息;
  • id : 此输入的可选标识符。默认为阶段名称。
  • ok : 输入表单上“确定”按钮的可选文本。
  • Parameter : 提示提交者提供的可选参数列表。请参阅参数以获取更多信息。
  • submitter : 可选的用逗号分隔的用户或允许输入此输入的外部组名的列表,默认为允许任何用户。
  • submitterParameter : 可以使用提交者名称设置的环境变量的可选名称(如果存在)。

基础示例:

代码语言:javascript复制
pipeline {
  agent any
  stages {
    stage('Example') {
      // 方式1.此种方式只能该块有效
      input {
        message "Title : 个人信息输入"
        ok "完成提交"
        submitter "alice,bob"
        parameters {
          string(name: 'INPUT_PERSON', defaultValue: 'WeiyiGeek', description: 'Message: 请输入您的姓名?')
          string(name: 'INPUT_AGE', defaultValue: 'WeiyiGeek', description: 'Message: 请输入您的年龄?')
          choice(name: 'INPUT_SEX', choices: ['Male','Female'], description: 'Message: 请选择你的性别?')
          booleanParam(name: 'INPUT_AGREE', defaultValue: true, description: 'Message: 是否确定协议?')
        }
      }

      steps {
        // 方式2: 注意script必须包含在 steps 块之中(此种方式可以全局传递参数)
        script {
          env.git_version=input message: 'Titel: 版本', ok: '通过',parameters: [string ( name: 'git_version', trim: true, description: 'Message : 请选择要操作的应用版本?')];

          env.deploy_option = input message: 'Titel: 操作', ok: 'deploy', parameters: [choice(name: 'deploy_option', choices: ['deploy', 'rollback', 'redeploy'], description: 'Message : 请选择操作流程?')];
        }

        // 12.input 输出示例
        echo "局部可用 输出示例1: 姓名:${INPUT_PERSON}, 年龄:${INPUT_AGE}, 性别:${INPUT_SEX}, 是否同意协议: ${INPUT_AGREE}"

        echo "全局可用 输出示例(script) -> 版本 : ${env.git_version}"
        echo "全局可用 输出示例(script) -> 版本 : ${env.deploy_option}"
    }
  }

  // 采用script块中定义input可以调用不同stage中得参数值
  stage ('调用') {
    steps {
      echo "调用1 : ${env.git_version}"
      echo "调用2 : ${env.deploy_option}"
    }
  }

}

WeiyiGeek.实际案例

when - 执行条件

描述: 该指令允许管道根据给定条件确定是否应执行该阶段,when指令必须至少包含一个条件,如果when指令包含多个条件,则所有子条件必须返回true才能执行该阶段;

使用嵌套条件构建更复杂的条件结构:not,allOf或anyOf,嵌套条件可以嵌套到任意深度。

  • 1.如果使用allOf条件,则表示所有条件为真才继续执行。
  • 2.如果使用anyOf条件,请注意一旦找到第一个“真”条件,该条件将跳过其余测试。
  • 3.使用使用not条件是,则当条件为false是为真才进行执行
代码语言:javascript复制
// 1.不匹配分支
when { not { branch 'master' } }

// 2.分别满足分支为master并且DEPLOY_TO环境变量值为production是继续执行
when { allOf { branch 'master'; environment name: 'DEPLOY_TO', value: 'production' } }

// 3.满足分支为master或者分支为staging都可以继续执行.
when { anyOf { branch 'master'; branch 'staging' } }

位置&参数:

代码语言:javascript复制
必须: No
参数: Express
允许: 在 Pipeline块 -> stage 块内

内置条件:

branch : 当正在构建的分支与给出的分支模式匹配时执行,请注意这仅适用于多分支管道;

代码语言:javascript复制
when { branch 'master' }

environment : 当指定的环境变量设置为给定值时执行,

代码语言:javascript复制
when { expression { return params.DEBUG_BUILD } }

equals : 当期望值等于实际值时执行阶段,

代码语言:javascript复制
when { equals expected: 2, actual: currentBuild.number }

expression : 在指定的Groovy表达式计算为true时执行阶段, 注意当从表达式返回字符串时,它们必须被转换为布尔值,或者返回null来计算为false。简单地返回"0"或"false"仍然会计算为"true"

代码语言:javascript复制
when { expression { return params.DEBUG_BUILD } }

Tag : 如果TAG_NAME变量匹配给定的模式则执行该阶段, 注意如果提供了一个空模式,那么阶段将在TAG_NAME变量存在时执行(与buildingTag()相同)。

代码语言:javascript复制
when { tag "release-*" }

buildingTag : 执行构建构建标签的阶段.

代码语言:javascript复制
when { buildingTag() }

changelog : 如果构建的SCM更改日志包含给定的正则表达式模式则执行阶段;

代码语言:javascript复制
when { changelog '.*^\[DEPENDENCY\] . $' }

changeset : 如果构建的SCM变更集包含一个或多个与给定模式匹配的文件,则执行阶段。

代码语言:javascript复制
when { changeset "**/*.js" }
when { changeset pattern: ".TEST\.java", comparator: "REGEXP" }  // 正则表达式匹配
when { changeset pattern: "*/*TEST.java", caseSensitive: true }   // 区分大小写ANT样式路径

changeRequest : 如果当前构建是针对“变更请求”的,则执行阶段(也称为GitHub和Bitbucket上的Pull Request,GitLab上的Merge Request,Gerrit变更等)。如果未传递任何参数,则阶段将在每个更改请求上运行

代码语言:javascript复制
when { changeRequest() }.
// # Possible attributes are `id, target, branch, fork, url, title, author, authorDisplayName, and authorEmail`

// 每一个都对应一个CHANGE_*环境变量,
when { changeRequest target: 'master' }.

// comparator 参数后指导属性,以指定匹配时如何计算任何模式:
// * EQUALS用于简单的字符串比较(默认值),
// * GLOB用于ANT风格的路径GLOB(与例如changeset相同),
// * REGEXP用于正则表达式匹配
when { changeRequest authorEmail: "[\w_-.] @example.com", comparator: 'REGEXP' }

triggeredBy : 在给定的参数触发当前构建时执行该阶段。

代码语言:javascript复制
when { triggeredBy 'SCMTrigger' }

when { triggeredBy 'TimerTrigger' }

when { triggeredBy 'UpstreamCause' }

when { triggeredBy cause: "UserIdCause", detail: "vlinde" }

优先级说明

1.when在input指令前评估 : 默认情况下,如果定义了阶段则在输入之前不会评估阶段的when条件。但是可以通过beforeInput在when块中指定选项来更改此设置。如果beforeInput设置为true,则将首先评估when条件并且仅当when条件评估为true时才输入

代码语言:javascript复制
pipeline {
    agent none
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            when {
                beforeInput true    // 关键点
                branch 'production'
            }
            input {
                message "Deploy to production?"
                id "simple-input"
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

2.when在options指令前评估 : 默认情况下,when一个条件stage会进入之后进行评价options为stage,如果任何限定。但是可以通过beforeOptions在when 块中指定选项来更改此设置。如果beforeOptions将设置为true,when则将首先评估条件,并且options仅在 when 条件评估为true时才输入

代码语言:javascript复制
pipeline {
    agent none
    stages {
      stage('Example Build') {
          steps {
              echo 'Hello World'
          }
      }
      stage('Example Deploy') {
          when {
              beforeOptions true   // 关键点
              branch 'testing'
          }
          options {
              lock label: 'testing-deploy-envs', quantity: 1, variable: 'deployEnv'
          }
          steps {
              echo "Deploying to ${deployEnv}"
          }
      }
  }
}

3.when在stage进入agent前评估 : 默认情况下,如果定义了一个阶段的when条件,那么将在进入该阶段的代理之后计算。但是,这可以通过在when块中指定beforeAgent选项来更改。如果beforeAgent被设置为true,那么将首先计算when条件,只有当when条件计算为true时才会输入agent

代码语言:javascript复制
pipeline {
    agent none
    stages {
        stage('Example Build') {
            steps {
                echo 'Hello World'
            }
        }
        stage('Example Deploy') {
            agent {
                label "some-label"
            }
            when {
                beforeAgent true
                branch 'production'
            }
            steps {
                echo 'Deploying'
            }
        }
    }
}

示例.Condition When

代码语言:javascript复制
pipeline {
    agent none  // beforeAgent
    stages {
      stage('Example Build') {
        steps {
          echo 'Hello World'
        }
      }
      stage('Example Deploy') {
        // Single Condition - 单一条件
        when {
          branch 'production'
        }

        // Multiple Condition - 多重条件
        when {
          branch 'production'
          environment name: 'DEPLOY_TO', value: 'production'
        }

        // Nested condition - 嵌套条件
        when {
          allOf {
            branch 'production'
            environment name: 'DEPLOY_TO', value: 'production'
          }
        }

        // Multiple condition and nested condition - 注意点
        when {
            branch 'production'
            anyOf {
                environment name: 'DEPLOY_TO', value: 'production'
                environment name: 'DEPLOY_TO', value: 'staging'
            }
         }
        
        // Expression condition and nested condition  - 表达式条件和嵌套条件
        when {
          expression { BRANCH_NAME ==~ /(production|staging)/ }
          anyOf {
              environment name: 'DEPLOY_TO', value: 'production'
              environment name: 'DEPLOY_TO', value: 'staging'
          }
        }

        // triggeredBy
        when {
          triggeredBy "TimerTrigger"  // 当前Job设置的触发器名称进行监控
        }

        // 当前面的when都满足时采用执行该阶段步骤,否则丢弃
        steps {
            echo 'Deploying'
        }
      }
    }
}

Tips : GLOB(对于默认)对于不区分大小写的ANT样式路径所以可以使用caseSensitive参数将其关闭;

2.3) Sequential Stages - 顺序阶段

描述: 声明式管道中的阶段可能有一个包含要按顺序运行的嵌套阶段列表的stage节。注意,一个阶段必须有且只有一个步骤、阶段、并行或 Matrix。如果stage指令嵌套在一个并行块或 Matrix 块本身中, 则不可能在stage指令中嵌套一个并行块或 Matrix 块。然而一个并行或 Matrix 块中的stage指令可以使用stage的所有其他功能,包括代理、工具、when等。

示例:Sequential Stages, Declarative Pipeline

代码语言:javascript复制
pipeline {
    agent none
    stages {
        // 非时序的阶段
        stage('Non-Sequential Stage') {
            agent {
                label 'for-non-sequential'
            }
            steps {
                echo "On Non-Sequential Stage"
            }
        }
        // 顺序阶段
        stage('Sequential Stage') {
            agent {
                label 'for-sequential'
            }
            environment {
                FOR_SEQUENTIAL = "some-value"
            }
            // 注意点1:
            stages {
                stage('In Sequential 1') {
                    steps {
                        echo "In Sequential 1"
                    }
                }
                stage('In Sequential 2') {
                    steps {
                        echo "In Sequential 2"
                    }
                }
                // 注意点2:可以使用stage的所有其他功能,包括代理、工具、when等
                stage('Parallel In Sequential') {
                    // 并行执行上面已讲述
                    parallel {
                        stage('In Parallel 1') {
                            steps {
                                echo "In Parallel 1"
                            }
                        }
                        stage('In Parallel 2') {
                            steps {
                                echo "In Parallel 2"
                            }
                        }
                    }
                }
            }
        }
    }
}
2.4) Parallel - 并行

描述:声明式管道中的阶段可能有一个包含要并行运行的嵌套阶段列表的并行部分。注意,一个阶段必须有且只有一个步骤、阶段、并行或 Matrix 。如果stage指令嵌套在一个并行块或 Matrix 块本身中,则不可能在stage指令中嵌套一个并行块或 Matrix 块。然而,一个并行或 Matrix 块中的stage指令可以使用stage的所有其他功能,包括代理、工具、when等。

Tips : 此外,通过在包含并行的阶段中添加failFast true,可以在任何一个阶段失败时强制终止所有并行阶段。添加failfast 的另一个选项是在管道定义中添加一个option{ parallelsAlwaysFailFast() }

示例:Parallel Stages, Declarative Pipeline

代码语言:javascript复制
pipeline {
    agent any
    //方式1.parallelsAlwaysFailFast
    options {
        parallelsAlwaysFailFast()
    } 
    stages {
        stage('Non-Parallel Stage') {
            steps {
                echo 'This stage will be executed first.'
            }
        }
        stage('Parallel Stage') {
            when {
                branch 'master'
            }
            // 方式2.在任何一个阶段失败时强制终止所有并行阶段
            failFast true
            // 并行构建值得学习
            parallel {
                stage('Branch A') {
                    agent {
                        label "for-branch-a"
                    }
                    steps {
                        echo "On Branch A"
                    }
                }
                stage('Branch B') {
                    agent {
                        label "for-branch-b"
                    }
                    steps {
                        echo "On Branch B"
                    }
                }
                stage('Branch C') {
                    agent {
                        label "for-branch-c"
                    }
                    stages {
                        stage('Nested 1') {
                            steps {
                                echo "In stage Nested 1 within Branch C"
                            }
                        }
                        stage('Nested 2') {
                            steps {
                                echo "In stage Nested 2 within Branch C"
                            }
                        }
                    }
                }
            }
        }
    }
}
2.5) Matrix - 模型

描述: 声明式管道(Declarative pipeline)中的阶段可能有一个 Matrix 节,定义要并行运行的名称-值组合的多维 Matrix 。我们将把这些组合称为 Matrix 中的“细胞”。 Matrix 中的每个单元可以包括一个或多个阶段,使用该单元的配置按顺序运行。注意一个阶段必须有且只有一个步骤、阶段、并行或 Matrix 。如果stage指令嵌套在一个并行块或 Matrix 块本身中,则不可能在stage指令中嵌套一个并行块或 Matrix 块。然而,一个并行或 Matrix 块中的stage指令可以使用stage的所有其他功能,包括代理、工具、when等。

此外,通过在包含 Matrix 的阶段同样也可添加 failFast true,您可以强制您的 Matrix 单元在其中任何一个失败时全部终止。添加failfast的另一个选项是在管道定义中添加一个option { parallelsAlwaysFailFast() } Matrix 部分必须包括一个轴部分和一个级部分。

  • axis部分定义了 Matrix 中每个轴的值。
  • stage部分定义了要在每个单元格中按顺序运行的阶段列表。

Tips : 同时Matrix 可以有一个exclude节来移除无效的单元格, 舞台上可用的许多指令,包括代理、工具、何时等,也可以添加到matrix中来控制每个单元格的行为。

axis 描述: axes(轴线)部分指定了一个或多个axis指令。每个轴由一个名称和一个值列表组成。每个轴上的所有值都与其他轴上的值组合起来生成单元格。

stages 描述: 该阶段部分指定每个单元中要顺序执行的一个或多个阶段, 此部分与前面任何stages是相同的;

示例: Matrix

代码语言:javascript复制
// One-axis with 3 cells, each cell runs three stages - "build", "test", and "deploy"
matrix {
    axes {
      axis {
        name 'PLATFORM'
        values 'linux', 'mac', 'windows'
      }
    }
    stages {
        stage('build') {
            // ...
        }
        stage('test') {
            // ...
        }
        stage('deploy') {
            // ...
        }
    }
}


// Three-axis matrix with 24 cells (three by four by two)
// 即 24 种组合方式: 例如 linux -> 32-bit -> chrome , mac -> 32-bit -> chrome 
matrix {
  axes {
    axis {
        name 'PLATFORM'
        values 'linux', 'mac', 'windows'
    }
    axis {
        name 'BROWSER'
        values 'chrome', 'edge', 'firefox', 'safari'
    }
    axis {
        name 'ARCHITECTURE'
        values '32-bit', '64-bit'
    }
  }
    // ...
}

excludes (optional) - 排出 描述: 可选的exclude部分允许作者指定一个或多个exclude filter表达式,这些表达式选择要从扩展的矩阵单元格集合中排除的单元格(aka, sparsening)。过滤器是使用一个或多个带有名称和值列表的排除轴指令的基本指令结构来构造的。 exclude中的axis指令生成一组组合(类似于生成矩阵单元格)。匹配排除组合中所有值的矩阵单元格从矩阵中移除。如果提供了多个exclude指令,则每个指令将分别计算以删除单元格。

当处理一长串要排除的值时 exclude axis指令可以使用 notValues 代替 values.这将排除与传递给notValues的值之一不匹配的单元格。

示例.具有24个单元的三轴矩阵,不包括“ 32位,mac”(不包括4个单元)

代码语言:javascript复制
matrix {
  axes {
      axis {
          name 'PLATFORM'
          values 'linux', 'mac', 'windows'
      }
      axis {
          name 'BROWSER'
          values 'chrome', 'edge', 'firefox', 'safari'
      }
      axis {
          name 'ARCHITECTURE'
          values '32-bit', '64-bit'
      }
  }
  excludes {
      exclude {
          axis {
              name 'PLATFORM'
              values 'mac'
          }
          axis {
              name 'ARCHITECTURE'
              values '32-bit'
          }
      }

      //排除linux和safari组合,并排除使用edge浏览器的任何非windows平台。
       exclude {
          // 2 cells
          axis {
              name 'PLATFORM'
              values 'linux'
          }
          axis {
              name 'BROWSER'
              values 'safari'
          }
        }
        exclude {
          // 3 more cells and '32-bit, mac' (already excluded)
          axis {
              name 'PLATFORM'
              notValues 'windows' // 注意点 norValues
          }
          axis {
              name 'BROWSER'
              values 'edge'
          }
        }
  }
    // ...
}
Matrix 单元级指令(可选)

描述: 通过在Matrix本身下添加阶段级指令,用户可以有效地为每个单元配置整体环境。这些指令的行为与它们在舞台上的行为相同,但它们也可以接受矩阵为每个单元格提供的值。

注意 axis和exclude指令定义了组成矩阵的静态单元格集, 这组组合是在管道运行开始之前生成的。另一方面“per-cell”指令在运行时进行计算。

directives include:

  • agent
  • environment
  • input
  • options
  • post
  • tools
  • when

Example.完整的矩阵示例,声明性管道

代码语言:javascript复制
pipeline {
    // 参数选择
    parameters {
        choice(name: 'PLATFORM_FILTER', choices: ['all', 'linux', 'windows', 'mac'], description: 'Run on specific platform')
    }
    agent none
    stages {
        stage('BuildAndTest') {
            matrix {
                agent {
                    label "${PLATFORM}-agent"
                }
                when { anyOf {
                    // 参数选择不为空时继续执行
                    expression { params.PLATFORM_FILTER == 'all' }
                    expression { params.PLATFORM_FILTER == env.PLATFORM }
                } }
                // 2 轴 (3 x 4) 十二格组合方式
                axes {
                    axis {
                        name 'PLATFORM'
                        values 'linux', 'windows', 'mac'
                    }
                    axis {
                        name 'BROWSER'
                        values 'firefox', 'chrome', 'safari', 'edge'
                    }
                }
                // 排除指定Matrix矩阵中的值 (排除linux和safari组合,并排除使用edge浏览器的任何非windows平台。) 
                // 即 排除 (linux,safari) , (linux,edge) , (mac,edge) 三种情况
                excludes {
                    exclude {
                        axis {
                            name 'PLATFORM'
                            values 'linux'
                        }
                        axis {
                            name 'BROWSER'
                            values 'safari'
                        }
                    }
                    exclude {
                        axis {
                            name 'PLATFORM'
                            notValues 'windows'
                        }
                        axis {
                            name 'BROWSER'
                            values 'edge'
                        }
                    }
                }
                stages {
                    stage('Build') {
                        steps {
                            echo "Do Build for ${PLATFORM} - ${BROWSER}"
                        }
                    }
                    stage('Test') {
                        steps {
                            echo "Do Test for ${PLATFORM} - ${BROWSER}"
                        }
                    }
                }
            }
        }
    }
}

WeiyiGeek.Matrix示例

语法总结
代码语言:javascript复制
// Declarative Pipeline Syntax Composite sample
pipeline {
  // 1.由于此处没有其它jenkins slave 分布式机器则采用any进行替代
  agent any
  // 7.全局环境变量
  environment { 
    global_env = 'Jenkins global environment'
    global_when = 'true'
  }
  // 8.全局选项
  options { 
    // 如果失败重试整个 Pipeline指定次数
    retry(3)
    // 在任何一个阶段失败时强制终止所有并行阶段
    parallelsAlwaysFailFast()

  }
  // 9.全局参数
  parameters {
    // 参数声明定义
    string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
    booleanParam(name: 'TOGGLE', defaultValue: true, description: 'Toggle this value')
    text(name: 'BIOGRAPHY', defaultValue: '', description: 'Enter some information about the person')
    choice(name: 'CHOICE', choices: ['One', 'Two', 'Three'], description: 'Pick something')
    password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'Enter a password')
  }
  
  // 11.触发器 (每两分钟执行一次)
  triggers{ cron('H/2 * * * *') }
  
  // 12.工具路径并放置环境变量到PATH
  // Dashboard -> 全局工具配置
  tools {
    maven 'maven_env'
    jdk   'jdk_1.8.0_211'
  }
  // 2.多阶段唯一入口
  stages {
    // 3.指定的单阶段(init-初始化阶段)
    stage ('init') {
      // 4.单阶段中的只有一个步骤steps
      steps {
        // 5.内置指令例如 echo 、println进行输出
        echo "init - steps one - built-in functions echo"
        println "init - steps one - built-in functions println "

        // 6.采用script指令来执行Scripted Pipeline中的一些脚本
        script {
          def browsers = ['chrome', 'firefox', 'edge']
          for (int i = 0; i < browsers.size();   i) {
              echo "Testing the ${browsers[i]} browser"
          }
        }
      }
    }

   stage ('one-stage') {
      // 7.局部环境变量
      environment { 
        // 变量定义
        local_env = "Jenkins local environment"
        // 将在Jenkins中设置的用户密码凭证赋予local_creds变量, 注意括号中为凭据名称
        // Dashboard -> 凭据 -> 系统 -> 全局凭据 (unrestricted) -> 添加凭据
        // Username with password	: 43287e62-ce5b-489a-9c11-cedf38e16e92	weiyigeek/****** (Pipeline 测试 credentials 读取)	Username with password	Pipeline 测试 credentials 读取
        Local_userpass_creds = credentials('43287e62-ce5b-489a-9c11-cedf38e16e92')
        // SSH Username with private key : b4c8b4e9-2777-44a1-a1ed-e9dc21d37f4f	weiyigeek (myself-gitlab-weiyigeek)	SSH Username with private key	myself-gitlab-weiyigeek
        Local_ssh_creds = credentials('b4c8b4e9-2777-44a1-a1ed-e9dc21d37f4f')
      }

      steps {
        println "全局变量: "   global_env  ", 局部变量: "   local_env 
        /* == 全局变量: Jenkins global environment, 局部变量: Jenkins local environment*/
        // 注意点: defined: MYVARNAME_USR and MYVARNAME_PSW respectively. (输出的字符串已经经过转义)
        sh 'echo "user is $Local_userpass_creds_USR"'      /* = user is **** */
        sh 'echo "password is $Local_userpass_creds_PSW"'  /* = password is **** */
        // 注意点: defined: MYVARNAME_USR and MYVARNAME_PSW (holding the passphrase).
        sh 'echo "SSH private key is located at $Local_ssh_creds"'
        sh 'echo "SSH user is $Local_ssh_creds_USR"'

        // 12.额外第三方工具插件路径调用方式
        script {
          scannerHome = tool 'sonarqubescanner'
          //这里这个tool是直接根据名称,获取自动安装的插件的路径
        }
        // withSonarQubeEnv('SonarQube') {
        //   sh "${scannerHome}/bin/sonar-scanner -Dsonar.projectKey=YourProjectKey -Dsonar.sources=."
        // }
      }


   }

    stage ('two-stage') {
      // 13.当任一满足以下条件时执行
      when {   
        beforeInput true 
        anyOf { branch 'master'; environment name: 'global_when', value: 'true' }
      }

      // 8.局部选项
      options {
        // 可设置全局(局部阶段)执行超时指定为一分钟,然后Jenkins将中止 Pipeline运行。
        timeout(time: 5, unit: 'MINUTES') 
      }

      // 12.Input指令输入参数到变量中
      // 方式1
      input {
          message "Title : 个人信息输入"
          ok "完成提交"
          submitter "alice,bob"
          parameters {
            string(name: 'INPUT_PERSON', defaultValue: 'WeiyiGeek', description: 'Message: 请输入您的姓名?')
            text(name: 'INPUT_AGE', defaultValue: 'WeiyiGeek', description: 'Message: 请输入您的年龄?')
            choice(name: 'INPUT_SEX', choices: ['Male','Female'], description: 'Message: 请选择你的性别?')
            password(name: 'INPUT_PASSWORD', defaultValue: 'SECRET', description: 'Message: 请输入您注册密码?')
            booleanParam(name: 'INPUT_AGREE', defaultValue: true, description: 'Message: 是否确定协议?')
          }
      }

      steps {
          // 方式2:注意script必须包含在 steps 块之中
          script {
            env.git_version=input message: 'Titel: 版本', ok: '通过',parameters: [string ( name: 'git_version', trim: true, description: 'Message : 请选择要操作的应用版本?')];
            env.deploy_option = input message: 'Titel: 操作', ok: 'deploy', parameters: [choice(name: 'deploy_option', choices: ['deploy', 'rollback', 'redeploy'], description: 'Message : 请选择操作流程?')];
          }

        // 10.Parameters 参数调用
        echo "全局 - Hello ${params.PERSON}"
        echo "全局 - Toggle: ${params.TOGGLE}"
        echo "全局 - Choice: ${params.CHOICE}"
        echo "全局 - Password: ${params.PASSWORD}"
        echo "全局 - Biography: ${params.BIOGRAPHY}"

        // 12.input 输出示例
        echo "input 输出示例1: 姓名:${INPUT_PERSON}, 年龄:${INPUT_AGE}, 性别:${INPUT_SEX}, 是否同意协议: ${INPUT_AGREE}"
        echo "input 输出示例(script) -> 版本 : ${env.git_version}"
        echo "input 输出示例(script) -> 版本 : ${env.deploy_option}"
      }
    }

    stage ('three-stage') {
      // 14.Parallel Stages 并行执行
      // 方式2.failFast true 在任何一个阶段失败时强制终止所有并行阶段
      parallel {
        stage('parallel-Branch A') {
            steps {
                echo "On Branch A"
            }
        }
        stage('parallel-Branch B') {
            steps {
                echo "On Branch B"
            }
        }
        stage('parallel-Branch C') {
          stages {
              stage('嵌套Nested 1') {
                  steps {
                      echo "In stage Nested 1 within Branch C"
                  }
              }
              stage('嵌套Nested 2') {
                  steps {
                      echo "In stage Nested 2 within Branch C"
                  }
              }
          }
        }
      }
    }

    // 15.Matrix 矩阵模型实践
    stage ('four-stage') {
      input {
        message "Title : 部署平台"
        ok "完成提交"
        submitter "alice,bob"
        parameters {
          choice(name: 'PLATFORM_FILTER', choices: ['all', 'linux', 'windows', 'mac'], description: 'Run on specific platform')
        }
      }
      matrix { 
        when { anyOf {
                // 参数选择不为空时继续执行
                expression { params.PLATFORM_FILTER == 'all' }
                expression { params.PLATFORM_FILTER == env.PLATFORM }
              } 
        }
        // 16.2 轴 (3 x 2) 6种组合方式的模型
        axes {
          axis {
            name 'PLATFORM'
            values 'linux', 'windows', 'mac'
          }
          axis {
            name 'ARCHITECTURE'
            values '32-bit','64-bit'
          }
        }
        // 17.排除指定模型例如非64-bit的在windows平台的将被剔除
        excludes {
          exclude {
              axis {
                name 'PLATFORM'
                values 'windows'
              }
              axis {
                name 'ARCHITECTURE'
                notValues '64-bit'
              }
          }
        }
        // 18.还有5种情况依次进行构建
        stages {
          stage('Build') {
            steps {
              echo "matrix - Do Build for ${PLATFORM} - ${BROWSER}"
            }
          }
        }
      }
    }
  }

  // 6.消息通知
  post { 
    // # 总是执行的步骤
    always { 
      echo 'I will always say Hello again!' /* == I will always say Hello again!— Print Message */
    }
  }
}

输出结果:

代码语言:javascript复制
## init - stage
maven_env— Use a tool from a predefined Tool Installation  <1s
# Fetches the environment variables for a given tool in a list of 'FOO=bar' strings suitable for the withEnv step.<1s
jdk_k1.8.0_211— Use a tool from a predefined Tool Installation  <1s
# Fetches the environment variables for a given tool in a list of 'FOO=bar' strings suitable for the withEnv step.<1s
maven_env— Use a tool from a predefined Tool Installation  <1s
# Fetches the environment variables for a given tool in a list of 'FOO=bar' strings suitable for the withEnv step.<1s
jdk_k1.8.0_211— Use a tool from a predefined Tool Installation <1s
# Fetches the environment variables for a given tool in a list of 'FOO=bar' strings suitable for the withEnv step.<1s
init - steps one - built-in functions echo— Print Message <1s
init - steps one - built-in functions println — Print Message <1s
Testing the chrome browser— Print Messag e<1s
Testing the firefox browser— Print Message 1s
Testing the edge browser— Print Message <1s


## one - stage
全局变量: Jenkins global environment, 局部变量: Jenkins local environment
  echo user is ****
user is ****
  echo **** is ****
**** is ****
  echo SSH private key is located at ****
SSH private key is located at ****
  echo SSH user is ****
SSH user is ****


## two-stage
全局 - Hello Mr Jenkins— Print Message<1s
全局 - Toggle: true— Print Message<1s
全局 - Choice: One— Print Message<1s
全局 - Password: SECRET— Print Message<1s
全局 - Biography: — Print Message<1s
input 输出示例1: 姓名:WeiyiGeek, 年龄:185, 性别:Male, 是否同意协议: true— Print Message<1s
input 输出示例(script) -> 版本 : v1.2— Print Message<1s
input 输出示例(script) -> 版本 : deploy— Print Message

## three-stage
On Branch A— Print Message
On Branch B— Print Message
In stage Nested 1 within Branch C
In stage Nested 2 within Branch C

## four-stage
I will always say Hello again!— Print Message<1s

WeiyiGeek.结果一览


0x03 pipeline 内置支持

3.0) 字符串和标准输出

echo: Print Message

println: Print Message

代码语言:javascript复制
echo "Hello"
println "World!"

脚本中操作字符串替换值 描述: 在使用 Groovy 语法写 Pipleline 脚本时候,可能需要替换先前设置好的一些文本的值,此处我们简单演示一下:

代码语言:javascript复制
script {
  // 测试的字符串
  sourceStr = "这是要替换的值:#value1, 这是要替换的值:#value2"
  // 替换 #value1 与 #value2 两个值
  afterStr = sourceStr.replaceAll("#value1","AAA").replaceAll("#value2","BBB")
  // 输出替换后的字符串
  print "${afterStr}"
  // 这是要替换的值:AAA,这是要替换的值:BBB
}

3.1) 文件目录相关步骤

isUnix: 如果封闭节点运行在类unix系统(如Linux或Mac OS X)上,则返回true,如果Windows。

pwd:确认当前目录

dir: 默认pipeline工作在工作空间目录下,dir步骤可以让我们切换到其他目录。

deleteDir:是一个无参步骤删除的是当前工作目录。通常它与dir步骤一起使用,用于删除指定目录下的内容。

代码语言:javascript复制
dir("./delete_dir")
deleteDir()

fileExists:检查给定的文件(作为当前目录的相对路径)是否存在。参数file返回 true | false

代码语言:javascript复制
# 工作空间中文件的相对(/分隔)路径,以验证文件是否存在。
fileExists file: "./pom.xml"

writeFile:将内容写入指定文件中; 参数为:file, text, encoding

readFile:读取文件内容; 参数为:file, encoding

代码语言:javascript复制
writeFile encoding: 'utf-8', file: 'file', text: '测试写入'

3.2) 制品相关步骤

stash : 步骤可以将一些文件保存起来以便被同一次构建的其他步骤或阶段使用。

代码语言:javascript复制
name
Name of a stash. Should be a simple identifier akin to a job name.
    Type: String

allowEmpty (optional)
    Type: boolean

excludes (optional)
Optional set of Ant-style exclude patterns.
Use a comma separated list to add more than one expression.
If blank, no files will be excluded.
    Type: String

includes (optional)
Optional set of Ant-style include patterns.
Use a comma separated list to add more than one expression.
If blank, treated like **: all files.
The current working directory is the base directory for the saved files, which will later be restored in the same relative locations, so if you want to use a subdirectory wrap this in dir.
    Type: String

useDefaultExcludes (optional)
If selected, use the default excludes from Ant - see here for the list.
    Type: boolean

unstash:恢复以前存储在当前工作区中的一组文件。

代码语言:javascript复制
# name: 以前保存的仓库的名称。
unstash 'repo'

wrap: 一般构建包装,它是特殊的步骤允许调用构建包装器(在freestyle或类似项目中也称为“环境配置”)

代码语言:javascript复制
wrap([$class: 'AnsiColorBuildWrapper']).

archive: Archive artifacts-归档的工件

unarchive: Copy archived artifacts into the workspace-将存档工件复制到工作区中

archiveArtifacts: 存档构建的成品 (重点)

代码语言:javascript复制
# 参数: String includes, optional: excludes 
archive
# 参数: Type: java.util.Map<java.lang.String, java.lang.String> mapping 
unarchive
# 参数: 
# artifacts: 用于存档的文件(支持通配符)
# excludes: 不包含的文件(支持通配符)
# allowEmptyArchive: 归档为空时不引起构建失败
# onlyIfSuccessful: 只有构建成功时归档
# fingerprint: 记录所有归档成品的指纹
# Follow symbolic links: 通过禁用此选项,工作空间中找到的所有符号链接都将被忽略。
archiveArtifacts artifacts: './target/*.jar', excludes: './target/test/*', fingerprint: true, onlyIfSuccessful: true, allowEmptyArchive: true

3.3) 命令相关步骤

描述: 与命令相关的步骤其实是 Pipeline:Nodes and Processes 插件提供的步骤。由于它是 Pipeline 插件的一个组件,所以基本不需要单独安装

withEnv: 设置环境变量 描述: 在块中设置一个或多个环境变量, 这些可用于该范围内生成的任何外部流程。例如:

代码语言:javascript复制
node {
  withEnv(['MYTOOL_HOME=/usr/local/mytool']) {
    sh '$MYTOOL_HOME/bin/start'
  }
}

Tips: 注意这里我们在Groovy中使用了单引号,所以变量展开是由Bourne shell完成的而不是Jenkins;

sh:执行shell命令 该步骤支持的参数有:

  • script:将要执行的shell脚本,通常在类UNIX系统上可以是多行脚本。
  • encoding:脚本执行后输出日志的编码,默认值为脚本运行所在系统的编码。
  • returnStatus:布尔类型,默认脚本返回的是状态码,如果是一个非零的状态码,则会引发pipeline执行失败。如果 returnStatus 参数为true,则不论状态码是什么,pipeline的执行都不会受影响。
  • returnStdout:布尔类型,如果为true,则任务的标准输出将作为步骤的返回值,而不是打印到构建日志中(如果有错误,则依然会打印到日志中)。除了script参数,其他参数都是可选的。

Tips: returnStatus与returnStdout参数一般不会同时使用,因为返回值只能有一个。如果同时使用则只有returnStatus参数生效。

Tips : 注意采用sh执行echo 1 > 1.txt命令时然后采用readFile读取时带有换行符,解决办法:

代码语言:javascript复制
# 方式1.采用 $? 判断命令执行成功与否。
# 方式2.采用echo命令输出到文件时加上 -n 选项。

bat、powershell步骤

  • bat步骤执行的是Windows的批处理命令。
  • powershell步骤执行的是PowerShell脚本,支持3 版本。

Tips: 步骤支持的参数与sh步骤的一样就不重复介绍了。

3.4) 其他步骤

异常终止 try-catch-finally : 异常捕获和抛出 error:主动报错中止当前 pipeline 并且避免打印堆栈跟踪信息。

代码语言:javascript复制
// 简单示例
try {
  sh 'might fail && whoami'
  echo 'Succeeded!'
} catch (err) {
  echo "Failed: ${err} - "   err.toString() 
  error "[-Error] : 项目部署失败 n[-Msg] : ${err.getMessage()} "
} finally {
  sh './tear-down.sh'
}

catchError: 捕获错误并将构建结果设置为失败

代码语言:javascript复制
catchError {
  sh 'might fail'
}

unstable: 设置阶段结果为不稳定, 将一条消息打印到日志中并将整个构建结果和阶段结果设置为不稳定。消息还将与阶段结果相关联并可能以可视化方式显示。

代码语言:javascript复制
unstable '阶段结果为不稳定'
unstable {
  echo "阶段结果为不稳定"
}

电子邮件

mail: Simple step for sending email.

代码语言:javascript复制
subject
Email subject line.
    Type: String
body
Email body.
    Type: String
bcc (optional)
BCC email address list. Comma separated list of email addresses.
    Type: String
cc (optional)
CC email address list. Comma separated list of email addresses.
    Type: String
charset (optional)
Email body character encoding. Defaults to UTF-8
    Type: String
from (optional)
From email address. Defaults to the admin address globally configured for the Jenkins instance.
    Type: String
mimeType (optional)
Email body MIME type. Defaults to text/plain.
    Type: String
replyTo (optional)
Reploy-To email address. Defaults to the admin address globally configured for the Jenkins instance.
    Type: String
to (optional)
To email address list. Comma separated list of email addresses. 
    Type: String

重试休眠和超时

retry:重试正文最多N次, 如果在块体执行过程中发生任何异常,请重试该块(最多N次)。如果在最后一次尝试时发生异常,那么它将导致中止构建(除非以某种方式捕获并处理它),不会捕获生成的用户中止。

sleep:让pipeline休眠指定的一段时间 , 只需暂停管道构建直到给定的时间已经过期相当于(在Unix上)sh 'sleep…'

timeout:以确定的超时限制执行块内的代码。

waitUntil:反复运行它的主体直至条件满足。

代码语言:javascript复制
# 参数: int count
retry(count: 5)  # 重试5次

# 参数: int time, optional unit ( Values: NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS)
sleep(time: 60,unit: SECONDS  # 休眠60s

# 参数: int time, optional activity, optional unit 
# Values: NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS
timeout(time: 10,unit: MINUTES) # 超时10分钟 

# 参数: optional initialRecurrencePeriod, optional quiet 
# initialRecurrencePeriod: 设置重试之间的初始等待周期(以毫秒为单位)默认为250 ms。每次失败都将降低尝试之间的延迟最多可达15秒。
# quiet: 如果为true,则该步骤不会在每次检查条件时记录消息。默认值为false。
waitUntil(initialRecurrencePeriod: 250, quiet: false){
  sh "./monitor.sh"
}

tool:使用预定义的工具,在Global Tool Configuration(全局工具配置)中配置了工具。

代码语言:javascript复制
tool name: 'Sonar-Scanner', type: 'hudson.plugins.sonar.SonarRunnerInstallation'
tool name: 'docker', type: 'dockerTool'

getContext: 从内部api获取上下文对象 withContext: 在块中使用内部api中的上下文对象

代码语言:javascript复制
# 参数: 用于受信任的代码,如全局库,它可以操作内部Jenkins api。
# Type: java.lang.Class<?>
# 例子:接受单个类型参数
getContext hudson.FilePath

# 例子:接受一个context参数和一个block。
# 在ConsoleLogFilter、LauncherDecorator和EnvironmentExpander的情况下,自动将其参数与上下文对象合并。
withContext(new MyConsoleLogFilter()) {
  sh 'process'
}

Tips : 不要试图传递在Groovy中定义的对象;只支持java定义的对象。实际上你应该避免使用this和getContext而只是在插件中定义一个步骤。

参考地址: https://www.jenkins.io/doc/pipeline/steps/workflow-basic-steps/


0x04 Pipeline 片段示例

(1) 超时设置与部署参数switch语句选择

代码语言:javascript复制
timeout(time: 1, unit: 'MINUTES') {
  script {
    env.deploy_option = input message: '选择操作', ok: 'deploy',
    parameters: [choice(name: 'deploy_option', choices: ['deploy', 'rollback', 'redeploy'], description: '选择部署环境')]
    switch("${env.deploy_option}"){
        case 'deploy':
            println('1.deploy prd env')
            break;
        case 'rollback':
            println('2.rollback env')
            break;
        case 'redeploy':
            println('3.redeploy env')
            break;
        default:
            println('error env')
    }
  }
}

(1)代码仓库拉取之checkout SCM

代码语言:javascript复制
checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'b4c8b4e9-2777-44a1-a1ed-e9dc21d37f4f', url: 'git@gitlab.weiyigeek.top:ci-cd/java-maven.git']]])

WeiyiGeek.流水线之代码拉取

(2) 代码质量检测之shell Script

代码语言:javascript复制
/usr/local/bin/sonar-scanner -Dsonar.projectName=${JOB_NAME} -Dsonar.projectKey=Hello-World -Dsonar.sources=.

// # 生成的流水线
sh label: 'sonar', returnStatus: true, script: '''/usr/local/bin/sonar-scanner -Dsonar.projectName=${JOB_NAME} -Dsonar.projectKey=Hello-World -Dsonar.sources=.'''

WeiyiGeek.流水线之代码质量检测

(3) Kubernetes 动态节点 Pod 模板的选择

代码语言:javascript复制
// # Scripted Pipeline
podTemplate(label: 'jenkins-jnlp-slave', cloud: 'k8s_115') {
  node ('jenkins-jnlp-slave') {
    stage ('dynamic-checkout') {
      checkout([$class: 'GitSCM', branches: [[name: '*/master']], userRemoteConfigs: [[credentialsId: '69c0dbf0-f786-4aa0-975a-76528f10de8b', url: 'http://127.0.0.1/xxx/devops_test.git']]])
    }
  }
}

0 人点赞