一.简介

当大量使用pipeline后,内置功能并不能照顾到所有需求,这时候需要扩展pipeline。

pipeline本质就是一个Groovy脚本。所以,可以在pipeline中定义函数,并使用Groovy语言自带的脚本特性。我们定义了createVersion函数,并使用了Date类

def createVersion(String BUILD_NUMBER) {
    return new Date().format( 'yyMM' ) + "-${BUILD_NUMBER}"
} pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                echo "${createVersion(BUILD_NUMBER)}"
            }
        }
    }
}

日志如下:

还有一种更优雅的写法,将变量定义在environment部分

def createVersion(String BUILD_NUMBER) {
return new Date().fromat( 'yyMM' ) + "-${BUILD_NUMBER}"
} pipeline {
agent any
environment {
_version = createVersion(BUILD_NUMBER)
}
stages {
stage('Build') {
steps {
echo "${_version}"
}
}
}
}

如果在一个Jenkinsfile定义一个函数,倒是无伤大雅。但是如果再20个Jenkinsfile中重复定义这个函数20遍,就有问题了。

二.共享库扩展

Jenkins pipeline提供了“共享库”(Shared library)技术,可以将重复代码定义在一个独立的代码控制仓库中,其他的Jenkins pipeline加载使用它。类似编程中的模块包(实际就是),可以引用其它方法,直接在当前pipeline使用。

创建共享库项目,目录结构如下

将代码推送到git仓库中,进入Jenkins的Manage Jenins-》Configure System -》Global Pipeline Libraries配置页面

配置项:

  • Name :共享库的唯一标识,在Jenkinsfile中会使用到。. Default version :默认版本。可以是分支名、tag标签等。
  • Load implicitly:隐式加载。如果勾选此项,将自动加载全局共享库,在Jenkinsfile中不需要显式引用,就可以直接使用。
  • Allow default version to be overridden :如果勾选此项,则表示允许“Default version”被Jenk-insfile中的配置覆盖。
  • lnclude@Library changes in job recent changes:如果勾选此项,那么共享库的最后变更信息会跟项目的变更信息一起被打印在构建日志中。

    .- Retrieval method:获取共享库代码的方法。我们选择Modern SCM”选项,进而选择使用Git仓库。

提示:除了可以使用Git仓库托管共享库代码,还可以使用SVN仓库托管。使用不同的代码仓库托管,“Default version”的值的写法不一样。本书只介绍Git仓库托管方式。

共享库使用

在pipeline里调用

@Library( 'global-shared-library')_
pipeline {
agent any
stages {
stage('Build') {
steps {
sayHello( "world")
}
}
}
}

在Jenkins pipeline的顶部,使用@Library指定共享库。注意,global-shared-library就是我们在上一个步骤中定义的共享库标识符。

引入共享库后,我们可以直接在Jenkins pipeline中使用vars目录下的sayHello,和Jenkins pipeline的普通步骤的使用方式无异。

至此,一个共享库的完整定义和基本使用就介绍完了。总结下来就四步:

1.按照共享库约定的源码结构,实现自己的逻辑。

2.将共享库代码托管到代码仓库中。

3.在Jenkins全局配置中定义共享库,以让Jenkins知道如何获取共享库代码。

4.在Jenkinsfile中使用@Library引用共享库。

使用@Library注解可以指定共享库在代码仓库中的版本。

@Library('global-shared-library@<version>')_

<version>可以是:

  • 分支,如@Library ( 'global-shared-library@dev')。
  • tag标签,如@Library ( 'global-shared-library@release1.0' )
  • git commit id,如@Library ( 'global-shared-library@e88d44e73fea304905dc00a1af 2197d945aa1a36')。

因为Jenkins支持同时添加多个共享库,所以@Library注解还允许我们同时引入多个共享库,如:@Library ( ['global-shared-library' , 'otherlib@abc1234])。

需要注意的是,Jenkins处理多个共享库出现同名函数的方式是先定义者生效。也就是说,如果global-shared-library与otherlib存在同名的sayHello,而@Library引入时global-shared-library在otherlib前,那么就只有global-shared-library的sayHello生效。

共享库结构

回顾目录

首先看vars目录。

放在vars目录下的是可以是从pipeline直接调用的全局变量,变量的文件名即为在pipline中调用的函数名,文件名为驼峰式的。

使用vars目录下的全局变量可以调用Jenkins pipeline的步骤。正如sayHello.groovy脚本,直接使用echo步骤

def call(String name = 'human') {
    echo "Hello, ${name}."
}

当我们在Jenkins中写sysHello("world")时,它实际调用的是sysHello.groovy文件中的call函数。

call函数还支持接收闭包(Closure),下例中,我们定义了一个mvn全局变量。

// vars/mvn.groovy
def call(mvnExec) {
    configFileProvider([configFile(fileId:'maven-global-settings', variable:'MAVEN_GLOBAL_ENV')]) {
        mvnExec("${MAVEN_GLOBAL_ENV}")
    }
}

以上call函数里的内容就是将configFileProvider啰嗦的写法封装在mvn变量中。这样我们就可以更简洁的执行mvn命令了。

@Libray('global-shared-library@master') _
pipeline {
    agent any
    tools {
        maven 'mvn-3.5.4
    }
    stages {
        stage('Build') {
            steps {
                mvn { settings ->
                    sh "mvn -s ${settings} clean install"
                }
            }
        }
    }
}

接着我们来看src目录

src目录是一个标准的java源码架构,目录中的类被称为库类Library class。而@Library('global-shared-library@dev')中的代表一次性静态加载src目录下的所又代码到classpath中。

Utils.groovy代码如下

package codes.showme
class Utils implements Serializable {
    def getVersion(String BUILD_NUMBER, String GIT_COMMIT){
        return new Date().format( 'yyMM' ) + "-${BUILD_NUMBER}" + "-${GIT_COMMIT}"
    }
}

提示:Utils实现了Serializable接口,是为了确保当pipeline被Jenkins挂起后能正确恢复。

在使用src目录中的类时,需要使用全包名。同时,因为写的是Groovy代码,所以还需要使用script指令抱起来。

@Library(['global-shared-library']) _
pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                script{
                    def util = new codes.showme.Utils()
                    def v = util.getVersion("${BUILD_NUMBER}", "${GIT_COMMIT}")
                    echo "${v}"
                }
            }
        }
    }
}

src目录中的类,还可以使用Groovy的@Grab注解,自动下载第三方依赖包

package codes.showme
@Grab(group='org.apache.commons', module='commons-lang3', version='3.6')
import org.apache.commons.lang3.StringUtils
class Utils implements Serializable {
    def isAlphanumeric(String aString){
        return StringUtils.isAlphanumeric(aString)
    }
}

不推荐大量使用@Grab,因为会带来维护困难的问题

pipeline模板

声明式pipeline在1.2版本后,可以在共享库中定义pipeline。通过此特性,我们可以定义pipeline的模板,根据不同的语言执行不同的pipeline。共享库代码如下:

// vars/generatePipeline.groovy
def cal1(String lang) {
if (lang== 'go') {
pipeline {
agent any
stages {
stage('set GOPATH') {
steps {
echo "GOPATH is ready"}
}
}
}
}
} } else if(lang == 'java ') {
pipeline {
agent any
stages {
stage('clean install') {
steps {
sh "mvn clean install"
}
}
}
}
}
}

使用时,Jenkinsfile就2行:

@Library(['global-shared-library'])_
generatePipeline('go ')

如果大多是项目的Jenkinsfile是标准化的,可以用模板方式

一些小问题

共享库方式,如果这个jenkinsfile在远程代码仓上,会把这些jenkinsfile都下载到job的工作目录下面

这样可能有些pipeline脚本名如果和项目名或者文件名重复的话,就会出问题。

例如git clone 下载的代码是和pipeline名字一致,就会导致下载失败

三.共享库例子

使用公共变量

1.变量文件

src/codes/showme/GlobalVars.groovy

//存储环境变量
package codes.showme //这里指定文件所在目录位置 class GlobalVars { //名称要和文件名一致
static String gitlab_url = "http://10.0.15.1"
static String script_deploy_edas = "/jen_script/deploy-edas.sh"
static String script_ding_notice = "/jen_script/dingding.py"
}

2.假如使用jenkinsfile直接调用

@Library('pipeline-library-demo')_

import codes.showme.GlobalVars

pipeline {
agent any
stages {
stage('pull') {
steps {
echo "${GlobalVars.gitlab_url}"
}
}
}
}

3.假如在共享库vars下的pipeline模板中用,先在jenkinsfile里引用共享库

jenkinsfile

@Library('pipeline-library-demo')_
PipelineTemp()

vars/PipelineTemp.groovy

import codes.showme.GlobalVars

def call() {
pipeline {
agent any stages {
stage('pull') {
steps {
script {
echo "${GlobalVars.gitlab_url}"
}
}
}
}
}
}

使用共享库的src方法

1.编写一个方法,用于处理操作的

src/codes/showme/DefExample.groovy

package codes.showme

//方法作用就是传参,然后打印这个参数
def readname(String BUILD_NUMBER) {
println(BUILD_NUMBER)
} return this //如果方法里没写return则这里要加这行

2.进行调用

jenkinsfile

@Library('pipeline-fuction') _

pipeline {
agent any
stages {
stage('Example') {
steps {
script {
def defex = new codes.showme.DefExample()
defex.readname("xxx")
}
}
}
}
}

3.如果放到pipeline模板里中则

def call() {
def defex = new codes.showme.DefExample()
pipeline {
agent any stages {
stage('Example') {
steps {
script {
defex.readname("xxxxx")
}
}
}
}
}
}

使用共享库的vars方法

1.编写,里面只能有一个call方法

vars/Ceshi.groovy

def call() {
println("xxxxxxxxxxxxxxxxxx")
}

2.调用,在jenkinsfile和pipeline模板中都是直接使用即可

@Library('pipeline-fuction') _

pipeline {
agent any
stages {
stage('pull') {
steps {
Ceshi()
}
}
}
}

四.插件实现pipeline

根据Jenkins插件的用法,将插件开发分成:通过界面使用插件和通过代码使用插件。

生成Jenkins插件代码骨架,非常简单,使用mvn命令就可以了。

mvn archetype:generate \
-DarchetypeGroupId=io.jenkins.archetypes \
-DarchetypeArtifactId=hello-word-plugin \
-DarchetypeVersion=1.4 \
-DartifactId=ansible-try \
-Dversion=1.0 \
-DinteractiveMode=false
HelloWorldBuilder: pipeline步骤具体实现的类
index.jelly: 在插件管理页面显示的HTML内容。

生成的代码骨架架构如下

HelloWorldBuilder : pipeline步骤具体实现的类

index.jelly :在插件管理页面显示的HTML内容

<?jelly escape-by-default='true'?>
<div>
    TODO
</div>

pom.xml:其name属性值就是插件管理页面中的插件名称。Jenkins插件代码骨架的默认值为

<name>TODO Plugin</name>

HelloWorldBuilderTest:单元测试类

接下来,我们来看HelloWorldBuilder类

public class HelloWorldBuilder extends Builder implements SimpleBuildStep {
    private final String name;
    private boolean useFrench;
    @DataBoundConstructor
    public HelloWorldBuilder(String name) {
        this.name = name;
    }
    public String getName() { return name; }
    public boolean isUseFrench() { return useFrench; }
    @DataBoundSetter
    public void setUseFrench(boolean useFrench) {
        this.useFrench = useFrench;
    }
    @Override
    public void perform(Run<?, ?> run,
        filePath workspace,
        Launcher launcher,
        TaskListener listener) throws InterruptedException, IOException {
            //插件的执行逻辑
    }
    @Override
    public BuildStepMonitor getRequiredMonitorService() {
        return BuildStepMonitor.BUILD;
    }
    @Symbol("greet")
    @Extension
    public static final class DescriptorImpl extends BuildStepMonitor<Builder> {
        //省略
    }
}
  • HelloWorldBuilder:需要继承Builder,并实现SimpleBuildStep接口的perform方法。
  • @DataBoundConstructor:标识插件类的构造函数,name属性为插件默认属性,也是调用插件时的必要参数
  • @DataBoundSetter:标识插件属性的setter方法。
  • getRequiredMonitorService:返回BuildStepMonitor枚举类型,BuildStepMonitor决定了perform方法的执行机制。BUILD的执行机制为
public boolean perform(BuildStep bs,AbstractBuild,Launcher launcher,BuildListener listener)
throws IOException, InterruptedException{
    if (bs instanceof Describable) {
        CheckPoint.COMPLETED.block(listener, ((Describable) bs).getDescriptor().getDisplayName());
    } else {
        CheckPoint.COMPLETED.block();
    }
    }
    return bs.perform(build,launcher,listener);
}

可以看出,在执行插件perform方法前,还做了不少工作。BuildStepMonitor还提供了其他值,具体可以看其源码。

  • Descriptorlmpl 内部静态类,用于高速Jenkins关于此插件的元数据。
  • @Extension:扩展点注解,Jenkins通过此注解自动发现扩展点,并将扩展点加入扩展点列表(ExtensionList)中。
  • @Symbol 一个扩展点的唯一标识,被定义在扩展点上,可以理解为在pipeline中使用插件时所引用的函数名。本次扩展点为greet

mvn hpi:run

如果写好一个插件,启动Jenkins,然后手动将Jenkins安装,测试。调整后,再卸载,安装,测试,会很慢

所以,Jenkins插件开发的代码骨架,默认提供了用于Jenkins插件开发的Maven插件:maven-hpi-plugin。通过该插件,只需要实现插件代码,然后执行mvn hpi:run,就可以启动一个安装了插件的Jenkins实例,直接访问jenkins即可。

进入Jenkins插件管理页面,可以在已安装列表中找到正在开发的插件。

如果需要对插件进行调整,也只是停止该Jenkins实例,再启动就行了。

如果进行单点调试,则结合IDE,使用Debug模式执行mvn hpi:run即可

greet步骤

Jenkins插件已经准备好了,现在开始使用插件。

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                greet 'build'
            }
        }
    }
    post {
        always {
            greet "always"
        }
    }
}

greet是我们通过@Symbol定义的插件扩展点名称。可以将这个名称理解为一个函数名。而使用@DataBoundConstructor注解的构造参数、@DataBoundSetter注解的setter方法,可以理解为这个函数的参数。

所以,greet "build"更完整的写法为:gree(name:"build", useFrench:false)。

值得注意的是,虽然插件继承的是Builder类,但是也可以直接在post部分使用。

插件全局配置

插件全局配置的作用在于简化了使用步骤的参数设置。就像在使用mail步骤时,不可能每次调用都带上邮箱服务器的配置,而且还有利于保持配置的一致性。

1.加入配置类

package io.jenkins.plugins.sample;
// 继承于GlobalConfiguration,@Extension注解一个都不能少
@Extension
public class HellowordGlobalConfig extends 继承于GlobalConfiguration {
    //自定义配置项,记得增加getter和setter方法
    private String language;
    public String getLanguage() { return language; }
    public void setLanguage(String language) { this.language = language; }
    //当用户打开全局页面时,从配置文件中加载配置
    public HellowordGlobalConfigW() {
        load();
    }
    @Overrid
    protected XmlFile getConfigFile() {
        Jenkins j = Jenkins.getInstance();
        if (j == null) {return null;}
        //一般将每个插件的全局配置文件保存在JENKINS_HOME目录下
        file rootDir = j.getRootDir();
        //插件全局配置文件名,每个插件各管各的全局配置
        File xmlFile = new File(rootDir, "jenkins.plugins.sample.HelloWorld.xml");
        return new XmlFile(xmlFile);
    }
    //工具方法,方便取HelloWorldGlobalConfig的值
    public static HellowordGlobalConfig get() {
        return GlobalConfiguration.all()
        .get(HellowordGlobalConfig.class);
    }
    //当用户在界面中保存配置时,将配置保存到全局配置文件中
    @Override
    public boolean configure(StaplerRequest req, JSONObject json) {
        req.bindJSON(this, json);
        save();
        return true;
    }
}

2.加入全局配置页面。在src/main/resources目录中,根据类路径创建HelloWorldGlob-alConfig子目录。结构如下:

HelloWorldGlobalConfig下的config.jelly内容如下:

<?jelly escape-by-default='true'?>
    <j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
        <f:section title="Hello world global config">
            <f:entry title="Language" field="language">
                <f:textbox />
            </f:entry>
        </f:section>
    </j:jelly>

进入系统设置页面,就可以找到我们的插件配置

3.使用配置 在HelloWorldBuilder类中通过HelloWorldGlobalConfig.get() .getLanguage() 拿到全局配置language的值。

@Override
public void perform(Run<?, ?> run, FilePath workspace, Launcher launcher, TaskListener listener) throws InterruptedExcaption, IOExcaption {
    String language = HelloWorldGlobalConfig.get().getLanguage();
    listener.getLogger().println(" Hello, " + name + "!," + language);
}

以下几种扩展pipeline的方式各有各的应用场景。

在Jenkins pipeline中自定义函数:简单、直观,但是容易产生重复代码。此种方式适合解决特定pipeline的问题。不推荐经常使用。

通过共享库扩展:在脚本中使用Jenkins现有的步骤,非常简单。此种方式适合对现有啰嗦的pipeline写法进行抽象,还适合做pipeline模板。

通过Jenkins插件扩展:适合需要高度定制化步骤的应用场景。

pipeline 共享库的更多相关文章

  1. 链接(extern、static关键词\头文件\静态库\共享库)

    原文链接:http://www.orlion.ga/781/ 一. 多目标文件的链接 假设有两个文件:stack.c: /* stack.c */ char stack[512]; int top = ...

  2. linux下共享库的注意点之-fpic

    在编译共享库必须加上-fpic.这是为什么呢? 首先看一个简单的例子: #include <stdio.h> int fun1() { printf("fun1\n") ...

  3. 《CMake实践》笔记三:构建静态库(.a) 与 动态库(.so) 及 如何使用外部共享库和头文件

    <CMake实践>笔记一:PROJECT/MESSAGE/ADD_EXECUTABLE <CMake实践>笔记二:INSTALL/CMAKE_INSTALL_PREFIX &l ...

  4. QT共享库的创建与调用(初级)(附:UI界面不能被改变的其中一个原因)

    背景: 最近在做的一个项目其中一部分既是实现PC与下位机的USB通信.windows平台下已经完成,现需移植到linux平台下. 在linux系统中,通过一段时间的工作,设备已被配置成hid类(后续再 ...

  5. ndk学习9: 动态使用共享库

    动态使用共享库函数 dll_main      环境介绍 续上节代码 目录结构:   android.mk如下: LOCAL_PATH := $(call my-dir) include $(CLEA ...

  6. linux共享库

    linux共享库 linux中共享库一般以.so.x.y.z 命名,其中x,y,z分别为主版本号.次版本号.发布版本号.同一个库,主版本号不同则相互不兼容:主版本相同,次版本号高的库比次版本号低的库有 ...

  7. linux C++ 共享库导出类

    1.共享库的对外接口函数的声明必须加上extern “C”. 2.使用共享库对话接口函数生成的对象指针时在该对象未被释放之前不能关闭共享库句柄,否则会出现segmentation fault错误. 以 ...

  8. 【转载】Linux下动态共享库加载时的搜索路径详解

    转载自:http://www.eefocus.com/article/09-04/71617s.html 对动态库的实际应用还不太熟悉的读者可能曾经遇到过类似“error while loading ...

  9. Linux 下编译安装软件,找不到共享库 xx.so 的解决办法

    编译memcached时,报错没有libevent,于是下载libevent,configure , make && make install ,然后在重新安装memcache成功之后 ...

随机推荐

  1. Django笔记&教程 2-4 视图常用

    Django 自学笔记兼学习教程第2章第4节--视图常用 点击查看教程总目录 1 - shortcut 视图函数需要返回一个HttpResponse对象或者其子类对象. 不过很多时候直接手写建立一个H ...

  2. Python 中的反转字符串:reversed()、切片等

    摘要:以相反的顺序反转和处理字符串可能是编程中的一项常见任务.Python 提供了一组工具和技术,可以帮助您快速有效地执行字符串反转. 本文分享自华为云社区<Python 中的反转字符串:rev ...

  3. BootStrap中模态框踩坑

    在模态框中使用html标签上的自定义属性来打开模态框后,在使用JS关闭模态框,就会出现多层蒙板问题 出现这个问题的原因就是没有仔细看bootstrap的官方文档,我人麻了,搞了好久 务必将模态框的 H ...

  4. nginx安装与配置1-nginx安装

    反向代理: 客户端不需要配置就可以访问,将请求发送到反向代理服务器, 由反向代理服务器选择目标服务器获取数据,再返回客户端,对外暴露代理服务器地址,隐藏真实ip 负载均衡: 客户端请求nginx等服务 ...

  5. CSS-sprit 雪碧图

    CSS-sprit 雪碧图  可以将 多个小图片统一保存到一个大图片中,然后通过调整background-position来显示响应的图片        这样图片会同时加载到网页中 就可以避免出现闪烁 ...

  6. JAVA特点及安装卸载

    C语言特点 1972 贴近硬件,运行速度快,效率高 操作系统,数据库,网络系统,编译器 指针和内存管理 C++语言特点 1982 面向对象 兼容C 图形领域,游戏等 Java语言特点 简单性 面向对象 ...

  7. 【机器学习与R语言】11- Kmeans聚类

    目录 1.理解Kmeans聚类 1)基本概念 2)kmeans运作的基本原理 2.Kmeans聚类应用示例 1)收集数据 2)探索和准备数据 3)训练模型 4)评估性能 5)提高模型性能 1.理解Km ...

  8. Python三元表达式,列表推导式,字典生成式

    目录 1. 三元表达式 2. 列表推导式 3. 字典生成式 3.1 字典生成式 3.2 zip()方法 1. 三元表达式 """ 条件成立时的返回值 if 条件 else ...

  9. EXCEL-COUNTIF()统计符合区间上的值个数

    =COUNTIF(D9:D21465,"<-0.2")+COUNTIF(D9:D21465,">0.2")  #计算<-0.2或者>0. ...

  10. 大型前端项目 DevOps 沉思录 —— CI 篇

    摘要 DevOps 一词源于 Development 和 Operations 的组合,即将软件交付过程中开发与测试运维的环节通过工具链打通,并通过自动化的测试与监控,减少团队的时间损耗,更加高效稳定 ...