工作接触Jenkins已经有一段时间,能够在工作中运用Jenkins搭建符合工作的生产线,但是感觉还是有很多地方懵懵懂懂,因此进一步学习《Jenkins2 权威指南》以提高对Jenkins更深层次的理解。
名称 | 地址 |
---|---|
Jenkins中文社区 | https://jenkins.io/zh/ |
在自由风格类型的任务中我们可以添加条件性构建步骤插件(Conditional BuildStep plugin)基于单个或者多个条件的构建步骤来定义更加复杂的流程。但是即便如此相比于我们编写程序时可以直接控制执行流程的方法,该插件对流程的控制依然有限。在本章节我们将探索Jenkins流水线DSL遇见所提供的用于控制流水线执行流程的不同结构 。
触发任务
指定流水线代码的触发事件,有如下3种不同的方法:
在其他项目构建后构建
//对于一个脚本式流水线,在任务Job1成功后构建流水线
properties([
pipelineTriggers([
upstream(
threshold:hudson.model.Result.SUCCESS
upstreamProjects:'Job1'
)
])
])
//多个任务可通过逗号隔开
//指定任务的某个分支可 Job1/master
周期性构建
/*cron类型功能,可以按照根据某个时间间隔启动任务
常用于以特别的间隔来启动任务来避免资源冲突
任务会在周一至周五上午9点运行*/
//脚本式流水线语法示例
properties([pipelineTriggers([cron('0 9 * * 1-5')])])
//声明式流水线语法示例
triggers{
cron(0 9 * * 1-5)}
//这种触发器和轮询触发器都使用Jenkins cron语法
//jenkins cron语法关于五字段说明
分钟(0-59) 小时(0-23) 日(1-31) 月(1-12) 周(0-7)
//*/语法可以用于某字段,表示"每隔"
//每隔5min
*/5
/*H可以用于任何字段(使用项目名称散列值计算出唯一的偏移量,用于错开执行具有相同时间的cron时间的项目)用于定义范围内的实际执行时间,避免所有配置相同cron值的任务在同一时间启动*/
//还可以给H符号附加范围用来指定可以选取的区间范围
//@yearly、@annually、@monthly、@weekly、@daily、@midnight和@hourly这些系统自定义的别名也都是用散列系统平衡
//深夜12点到早上7:59之间的某个时间点
H H(0-7) * * *
//每隔15min
H/15 * * * *
//前半个小时,每隔10min
H(0-29)/10 * * * *
//每个工作日从9:45AM开始到3:45PM结束。每隔两小时、每45min的时候执行一次
45 9-16/2 * * 1-5
//每个工作日9AM到5PM、每两个小时空挡执行一次
H H(9-16)/2 * * 1-5
//除12月外的每个月的第1天、第15天执行一次
//注意 */3 将会在一个31天月份的第1、第4、第31天执行,然后从后面一天开始下一个月的循环
//注意 散列值总是从1-28范围内取值,所以在H/3月末时距上一次运行可能会有3-6天的渠口
//示例
//在每小时的第15min时启动一个流水线
triggers{
cron(15 * * * *)}
//每20min间隔扫民一次SCM变化
triggers{
pollSCM(*/20 * * * *)}
//在每小时前半个小时里的某个时间点启动流水线
triggers{
cron(H(0,30) * * * *)}
//从周一到周五上午9点开始启动一个流水线
triggers{
cron(0 9 * * 1-5)}
使用GitHub钩子触发器进行GitSCM轮询
//代码推送到代码库就会发射推钩触发Jenkins,进而调用Jenkins SCM轮询相应功能,所以必须配置轮询才能使GitHub钩子触发器正常工作。更多信息可参见Jenkins Wiki文档
//脚本式流水线语法如下
properties([pipelineTriggers([githubPush()])])
SCM轮询
//周期性扫描源码版本控制系统的变更(若发现任何更新,任务都会处理这些变化)
//脚本式流水线语法如下(每30min轮询一次)
properties([pipelineTriggers([pollSCM('*/30 * * * *')])])
//声明式流水线语法如下(每30min轮询一次)
triggers{
pollSCM(*/30 * * * *)}
用户输入(脚本式流水线)
//参数message(消息):显示给用户的提示信息
//默认表单打印一条消息和提供给用户一个选择
input 'Continue to next stage ?'
//参数id(自定义ID):用于标识你的input步骤,便于自动化或者外部处理(例如通过REST API调用来响应)。若没有提供ID则系统也会生成一个唯一的标识符
//在input步骤定义中添加一个自定义的ID
input id:'ctns-prompt', message:'Continue to the next stage ?'
//在运行这个步骤的时候POST到这个URL的请求可以被用作响应
//告诉jenkins没有任何输入继续运行
http://[jenkins-base-url]/job/[job_name]/[build_id]/input/Ctns-prompt/proceedEmpty
//告诉jenkins中止运行
http://[jenkins-base-url]/job/[job_name]/[build_id]/input/Ctns-prompt/abort
//注意:如果你的Jenkins通过安全设置(推荐)开启了防止跨站请求伪造(CSRF),那么用于POST请求的任何URL也需要包含一个CSRF防卫令牌
//定义一个环境变量来获取这个令牌
CRSF_TOKEN=$(curl -s 'http://:@/crumbIssuer/api/xml?path=concat(//crumbRequestField,":",//crumb)' )
//随后查看环境变量中的令牌
$ echo $CSRF_TOKEN
Jeknins-Crumb:0co0usrdw98s34l0175a1m5io7xvn1
//然后可以在POST请求中包含这个令牌
$ curl --user <userid>:<password or token> -H "$CSRF_TOKEN" -X POST -s <jenkins base url>/job/<job name>/<build number>/input/<input parameter with 1st letter capped>/procceedEmpty
//如果没有包括令牌,你会得到一个403错误而终止
//参数ok(按钮字幕)
input message: '' , ok:'Yes'
//参数subbmit(允许的提交者)
//逗号分隔的用户ID或者组名列表,用于授权哪些人可以给予响应
input message:'' , subbmit:'user1,user2'
//参数submitterParameter(存储准提交者的参数)
//可以使用一个变量来存储批准执行的用户。如果没有其他参数,那么传输submitterParameter参数的名字就无关紧要
def resp = input id: 'ctns-prompt', message: 'Continue to the next stage?',submitterParameter:'approver'
echo "Answered by ${resp}"
//如果有任何其他的参数,那么必须提供submitterParameter的名字才能访问
//string型UI显示为输入框
def resp = input id: 'ctns-prompt', message: 'Continue to the next stage?', parameters:[string(defaultValue: '', description: '', name: 'para1')], submitterParameter: 'approver'
echo "Answered by " + resp['approver']
//boolean型UI显示为选项(语句返回java.lang.boolean)
def answer = input message: '' , parameters: [booleanParam(defaultValue: '', description: '', name: 'para2')]
//choice型UI显示为选项(第一个值会作为默认值)
def choice = input message: '' , parameters: [choice(choices: 'choice1\nchoice2\n', description: 'Choose an option', name: 'para3')]
//注意:Jenkins2.112之前的版本代码片段生成器为选项型参数生成错误代码,会导致java.lang.IllegalArgumentException报错
//参数List Subversion tag(列出Subversion标签)
//参数允许执行一个Subversion标签集合,在构建运行时可以从中选择一个。其子参数包括名称、代码仓库URL、凭证、标签过滤器、默认值、显示标签的最大值、以及按最新标签优先排序和(或)按字母顺序排序排列选项
//仓库URL子参数:jenkins期望Subversion仓库URL中包含所要显示的标签。若没有包含标签并且存在子文件夹的话,子文件夹将会被显示出来,以便能够进一步向下进行。若需要凭证访问代码仓库则凭证子参数可以包含凭证信息
//标签过滤器:用来过滤显示标签列表的正则表达式
//默认值:仅被用在SVN轮询或者类似特性需要默认值的时候
def tag = input message: '' , parameters: [[$class: 'ListSubversionTagsParameterDefinition', credentialsId:'jenkins2-ssh', defaultValue: '', maxTags: '', name: 'LocalSVN', reverseByDate: false, reverseByName: false, tagsDir: 'file:///svnrepos/gradle-demo', tagsFilter: 'rel_*']]
//参数password(密码)
//允许用户输入一个密码。密码文本会被隐藏起来
//密码输入示例
def pw = input message: '' , parameters: [password(defaultValue: '', description: 'Enter your password.' name: 'passwd')]
//多个输入参数的返回值
//若有多个参数,将会返回一个映射(map),可以通过参数名称抽取出每个参数的返回值
//多参数、使用不同方法输出语句中访问单独的返回值
def loginInfo = input message: 'Login', parameters: [string(defaultValue: '', description: 'Enter Userid: ', name: 'userid'),password(defaultValue: '', description: 'Enter Password:', name: 'passwd')]
echo "Username = " + loginInfo['userid']
echo "Password = ${loginInfo['passwd']}"
echo loginInfo.userid + " " + loginInfo.passwd
//参数run(运行)
//参数允许用户从一个任务中选择一个特定的运行(已经执行过的构建)。这个参数可能会被用在测试环境中
//使用过滤器的代码示例(P67)
//参数credential(凭证)
//可选的凭证类型包括任何、用户名和密码、Docker主机证书验证、SSH用户名及私钥、机密文件、机密文件和证书
//默认值得是默认的凭证
//SSH密钥示例(P64)、用户名和密码示例(P65)
//参数file(文件)
//这个参数允许用户选择一个文件给流水线使用。
//文件参数示例(P65)
//注意:这个参数类型返回的是一个hudson.FilePath对象。FilePath关联的一些方法在Jenkins脚本中是不允许执行的。需要管理员通过第5章所描9述的流程批准后才能使用
//参数text(Multiline String)
//允许用户输入多行文本
//多行字符串输入示例(P67)
用户输入(声明式)
//参数与声明式流水线
//由于创建一个新的本地变量用于存放input语句的返回值并不适用与声明式模型。因此如何在声明式流水线将如下讨论
//使用parameters指令
//简单示例语法demo1
pipeline {
agent any
parameters {
string(name: 'USERID', defaultValue: '', description: 'Enter your userid')
}
stages {
stage('Login') {
steps {
echo "Active user is now ${params.USERID}"
}
}
}
}
简单示例语法demo2
properties([
parameters ([
string(defaultValue: '', description: '', name: 'USERID')
])
])
pipeline {
agent any
stages {
stage('Login') {
steps {
echo "Active user is now ${params.USERID}"
}
}
}
}
//注意:这种方法生效范围只在Jenkins应用中并且是其中个别任务,因此不推荐在生产环境中使用。
//使用Jenkins应用来参数化构建
//即在基本配置部分中,勾选这个项目是参数化的(This project is parameterized)复选框,然后就可以照常在任务的Web界面上定义你的参数,然后无需在代码中输入语句就可以通过params.<参数名称>的形式直接饮用任务参数
//使用一个script代码块
//有些场景下声明式风格不支持或者实现起来非常困难。对于这种情况,声明式语法支持一个script代码块
//script代码块允许你在其中使用非声明式的语法 。其中包括定义变量,这在声明式流水线的script代码块中是不被允许的,这也意味着你不能在script代码块之外引用该代码块中定义的变量。然而,可以把script块中的返回值存储在一个环境变量中,然后可以在需要这个值得时候访问这个环境变量
stage ('Input') {
steps {
script {
env.RESP1 = input message: '' , parameters: [string(defaultValue: '', description: 'Enter response 1', name: 'RESPONSE1')]
env.RESP2 = input message: '' , parameters: [string(defaultValue: '', description: 'Enter response 2', name: 'RESPONSE2')]
echo "${env.RESP1}"
}
echo "${env.RESP2}"
}
}
//使用外部代码
//将脚本式语句(类似调用输入语句)存放在外部共享库中或者存放在一个可以执行的外部Groovy文件中
//例如我们可以把输入的处理编码写入一个文件中,该文件在共享库中被命名为vars/getUser.groovy,代码示例如下
#!/usr/bin/env groovy
def call(String prompt1 = 'Please enter your data', String prompt2 = 'Please enter your data') {
def resp = input message: '' , parameters: [string(defaultValue: '', description: 'prompt1', name: 'RESPONSE1'), string(defaultValue: '', description: 'prompt2', name: 'RESPONSE2')]
echo "${resp.RESPONSE1}"
echo "${resp.RESPONSE2}"
}
//如果我们的库被命名为Utilities然后就可以导入这个库并且调用getUser函数,代码如下所示
@Library('Utilities')
pipeline {
agent any
stages {
stage('Input') {
getUser 'Enter response1','Enter response2'
}
}
}
流程控制选项
//超时(timeout)
//timeout步骤允许开发者限制某个行为发生时所花费的时间,其默认单位是min(只指定一个时间值),如果发生超时该步骤就会抛出一个异常。如果该异常没有被处理,将导致整个流水线过程被终止
#常用来对任何可能造成流水线暂停的步骤进行封装
#这种情况下,Jenkins将会给用户10s反应时间,超时则会抛出异常终止流水线。超时示例代码如下
node {
def response
stage('input') {
timeout(time: 10, unit: 'SECONDS') {
response = input message: 'User',
parameters: [string(defualtValue: 'user1', description: 'Enter Userid', name: 'userid')]
}
echo "Username = " + response
}
}
//也可以配合try-catch代码块封装timeout,在处理异常的时候,把响应设置成期望的默认值
node {
def response
stage('input') {
try {
timeout(time: 10, unit: 'SECONDS') {
response = input message:'User',
parameters: [string(defualtValue: 'user1', description: 'Enter Userid', name: 'userid')]
}
} catch(err) {
response = 'user1'
}
}
}
//重试(retry)
//这个retry闭包将代码封装为一个步骤,当代码中有异常发生时,该步骤可以重试n次。如果达到限制且发生一个异常则整个过程会被终止
//重试代码如下
retry(<n>) {
//代码过程}
//睡眠(sleep)
//延时步骤,接受一个数值并且延时相应的时间后在继续执行。默认单位是s
//等待5s后处理代码如下所示
sleep time: 5, unit: 'MINUTES'
//等待直到(waitUntil)
//此步骤导致整个过程一直等待直到某件事发生。"某件事"指的是可以返回true的闭包,如果代码块中过程返回false,那么这个步骤会在更长的时间后进行下一次尝试。过程中抛出的任何异常都会导致这个步骤立即退出并且抛出一个异常
#等待直到语法代码如下图所示:
waitUntil {
//返回true或者false的过程}
//使用waitUntil代码块等待直到一个标记文件出现的示例
timeout(time: 15, until: 'SECONDS') {
waitUntil {
def ret = sh returnStatus: true, script: 'test -e /home/jenkins2/marker.txt'
return (ret == 0)
}
}
//如果过程返回false那么waitUtil步骤会等待更长的时间后进行下一次尝试。例如系统从0.25s等待时间开始,如果需要再次循环,会乘1.2的因子得到0.3,在之后的循环中,最近一次的等待时间乘1.2得到新时间
//再举个例子,加入我们要等待一个Docker容器运行起来,以便我们可以在流水线测试部分中通过一个REST API调用获取一些数据。在这种情况下,如果这个URL还不可用,我们就会得到一个异常。为保证异常被抛出的时候进程不会立即退出。代码示例如下
timeout(time: 120, unit: 'SECONDS') {
waitUnitl {
try {
sh "docker exes ${网页Container.id} curl --silent http://127.0.0.1:8080/roar/api/v1/registry 1>test/output/entries.txt"
return true
} catch (exception) {
retrun false
}
}
}
//注意:若此段代码在声明式流水线中执行则需要使用一个script代码块或共享库的方法去处理这段代码
处理并发
//使用lock步骤对资源加锁
//安装可锁定资源插件(Lockable Resources plugin)系统中就会有一个DSL lock步骤用来阻止多个构建在同一时间试图使用相同资源。
//这个DSL lock步骤是一个代码块步骤。它锁定特定资源直到完成闭包中的所有步骤。最简单的情况是你仅仅提供资源的名字作为默认的参数。代码示例如下
lock('worker_node1') {
//worker_node1上运行的一些步骤
}
//提供一个标签名称用于选择拥有该标签的一组资源,同时提供一个数值用于指定需要被上锁(预定)的匹配该标签对的资源数量
lock(label: 'docker-node', quantity: 3){
//一些步骤
}
//可以理解为"必须拥有多少这种资源才能继续前进",如果指定标签但没有指定数量,那么标记该标签所有资源都会被锁定
//inversePrecedence为可选参数,如果这个参数被设置为true,那么最新的构建优先获得资源(资源可用时)。否则按照申请顺序
//考虑使用一个声明式流水线就在我们想使用的某个特定的代理节点上运行构建,而不管有多少个流水线实例正在运行。代码如下
stage('Build') {
steps {
lock('worker_node1') {
sh 'gradle clean build -x test'
}
}
}
//使用milestone来控制并发构建
//在jenkins中还可能需要处理的一个场景就是同一流水线的多个并发在某一时刻构建而发生的资源竞争问题。这些运行可能在不同的时间点以不同的步骤到达某个关键点,或者一个运行可能修改了必要的资源,其他运行到达词典是发现资源的状态不对。总之我们不能保证在一个运行修改了资源之后,其他运行不会出现并且修改这个资源,而先前的运行还尚未完成
//milestone就是为了防止出现构建运行顺序混乱而踩踏的情况,如果较新的构建已经达到那里,系统就会阻止较老的构建通过
//在Gradle构建后放置一个milestione步骤代码示例
sh "'${gradleLoc}/bin/gradle' clean build"
}
milestone label: 'After build', ordinal: 1
stage("NotifyOnFailure") {
//在多分支流线中限制并发
//流水线DSL包含了一个可以限制多分支流水线每次只构建一个分支的方法。设置成功后,当前正在构建以外的其他分支申请的构建会被放入列表中插队
//在脚本式流水线语法中代码示例如下
properties([disableConcurrentBuilds()])
//在声明式流水线语法中代码示例如下
options {
disableConcurrentBuilds()}
//并行地运行任务(P82-P85 未看懂,待二次学习)
//流水线DSL中存在专门的结构支持并行地运行步骤(传统结构同时适用于脚本式和声明式流水线,一个较新结构仅适用于声明)
//stash和unstash(P85-P88 未看懂,待二次学习)
//声明式流水线中的替代并行语法(P88-P89 未看懂,待二次学习)
//parallel和failFast(P89-P92 未看懂,待二次学习)
有条件的执行功能
//在脚本式流水线代码中用if语句(如同时用Groovy或者java语言条件语句一样简单)进行条件判断
node ('work_node1') {
def responses = null
stage('selection') {
responses = input message: 'Enter branch and select build type',
parameters:[string(defaultValue: '', description: '', name: 'BRANCH_NAME'), choice(choices: 'DEBUG\nRELEASE\nTEST'), description: '', name: 'BUILD_TYPE']
}
stage('process') {
if((response.BRANCH_NAME == 'master') && (response.BUILD_TYPE == 'RELEASE')) {
echo "Kicking off production build\n"
}
}
}
//由于Groovy/Java特定语言特定并不适用与声明式模型,因此声明式提供相对应的实现语句
pipeline {
agent any
parameters {
string (defaultValue: '', description: '', name: 'BRANCH_NAME')
choice (choices: 'DEBUG\nRELEASE\nTEST', description: '', name: 'BUILD_TYPE')
}
stages {
stage('process') {
when {
allOf {
expression {
params.BRANCH_NAME == 'master'}
expression {
params.BUILD_TYPE == 'RELEASE'}
}
}
steps {
echo "Kicking off production build\n"
}
}
}
}
构建后处理
//脚本式流水线构建后处理
//脚本式流水线常用Groovy程序结构(try-catch-finally)来实现构建后处理
def err = null
try {
//流水线代码
node('node-name') {
stage ('first stage') {
...
}//最后一个阶段结尾
}
}
catch(err) {
currentBuild.result = "FAILURE"
}
finally {
(currentBuild.result != "ABORTED") {
//在构建失败或不稳定时发送邮件通知
}
}
//注意:如果构建被终止,那么我们也不会发送邮件
//脚本式流水线提供了更高级的处理异常方法catchError
//若代码块抛出异常则构建会被标记为失败,但流水线中catchError代码块往后的语句可以继续执行(优点是:处理失败后依然可以发送邮件),代码示例如下
node ('node-name') {
catchError {
stage ('first stage') {
...
}//最后一个阶段结尾
//发送邮件通知
}
}
//等同于try-catch-finally语句中
node ('node-name') {
try {
stage ('first stage') {
...
}//最后一个阶段结尾
}catch (err) {
echo "Caught: ${err}"
currentBuild.result = 'FAILURE'
}
//发送邮件通知的步骤
}
//声明式流水线与构建后处理
//声明式流水线中有一个专门的部分可用于构建后处理
//构建后处理的声明式构建条件
---------------------------------------------------------------
条件 描述
always 总是执行代码块中的步骤
changed 如果当前构建状态与先前构建状态不同,则执行代码块中的步骤
success 如果当前构建状态为成功的,则构建代码块中的步骤
failure 如果当前构建状态为失败的,则构建代码块中的步骤
unstable 如果当前构建状态为不稳定的,则执行代码块中的步骤
//声明式流水线代码示例
}
}//阶段的结尾
post {
always {
echo "Build stage complete"
}
failure {
echo "Build failed"
mail body: 'build failed', subject: 'Build failed', to: '[email protected]'
}
success {
echo "Build success"
mail body: 'build failed', subject: 'Build failed', to: '[email protected]'
}
}//流水线结尾
Jenkins位置和电子邮件通知
(P99-P102 未实践,待二次学习)
在流水线中发送电子邮件
//脚本式流水线中使用基本mail步骤的示例
node ('worker_node1') {
try {
...
currentBuild.result = "SUCCESS"
} catch (err) {
currentBuild.result = "FAILURE"
} finally {
mail to: '[email protected]', subject: "Status of pipeline: ${currentBuild.fullDisplayName}", body: "${env.BUILD_URL} has result ${currentBuild.result}"
}
}
//声明式流水线中使用基本mail步骤的示例
pipeline {
agent any
stages {
...
}
post {
always {
mail to: '[email protected]', subject: "Status of pipeline: ${currentBuild.fullDisplayName}", body: "${env.BUILD_URL} has result ${currentBuild.result}"
}
}
}
拓展电子邮件通知
需安装电子邮件扩展插件(Extension Email plugin, email-ext),主要实现以下3功能(P104-P110 未实践,待二次学习)
内容(Content):可以动态地修改电子邮件通知的主题和正文内容
收件人(Recipient):可以定义那些角色的用户会收到电子邮件通知
触发器(Triggers):可以指定发送电子邮件通知的条件 (注意:这些目前不适用于流水线)
报告
Jenkins使用的许多插件或者工具都会为各种任务生成HTML报告。其中一些工具(如SonarQube)甚至可以和jenkins任务输出做定制集成,用户通过点击操作就可以跳转到应用程序本身并查看报告
HTML发布者插件(HTML Publisher plugin)允许用户在流水先代码中添加步骤来指向HTML报告,它也允许你在任务输出页面上创建自定义链接,并提供长时间保存报告(存档)等选项
发布报告
//例如按照惯例Gradle执行完单元测试后会创建名为index.xml的报告,并将其放在/build/reports/test目录中。对于流水线我们希望在Gradle生成的HTML测试报告中为api和util子项目添加链接
//这里我们需要提供传递给DSL的基本信息,该步骤名为publishHTML。针对api报告,调用步骤代码如下(常置于post代码块中)
publishHTML (target: [
//若设置为false,报告确实将会使构建失败
allowMissing: false,
//若设置为true,那么即使当前构建失败,jenkins也将显示上次成功构建的报告链接
alwaysLinkToLastBuild: false,
//若设置为true,那么jenkins会将所有成功构建的报告归档。否则只会归档最近成功构建的报告
keepAll: true,
//这是HTML文件相对与Jenkins工作空间的路径
reportDir: 'api/build/reports/test',
//这是要显示的HTML文件的名称(如果有多个名称应该用逗号隔开)
reportFiles: 'index.html'
//这是你希望在任务输出页面上看到的链接到的报告名称
reportName: "API Unit Testing Results"
])
在全局安全配置中,启动安全选项包含了最相关的功能,其安全性可以在两个维度上进行配置-身份验证和授权
访问控制-安全域
这里允许我们指定哪些实体负责验证Jenkins用户身份。又若干选项
访问控制-授权
5. 任何用户可以做任何事:所有用户(包括匿名用户)均无限制访问
6. 传统模式:任何具有管理员身份的人都具有完全控制权限,其他人只有读权限
7. 登录用户可以做任何事:用户必须先登录才能完全访问
8. 安全矩阵:允许用户通过矩阵排列中的复选框为单个用户或组指定非常详细的权限
9. 项目矩阵授权策略:此选项是"安全矩阵"模型的拓展,在每个项目配置页都添加矩阵
访问控制-其他全局安全配置
Jenkins中的凭证(P133-P171 未理解,待二次学习)
存储:凭证的API允许访问外部凭证存储(能够存储和获取凭证的应用程序)。然而,在默认情况下可以使用Jenkins的一个内部加密凭证存储(在JENKINS_HOME目录下,使用存储在JENKINS_HOME目录中的键进行加密,其secrets)