[持续交付实践] pipeline:pipeline 使用之 Shared Libraries

前言

随着pipeline交付流水线在团队中的推广,使用pipeline脚本的job也迅速增加。虽然我们已经基于公司的技术栈特点做了一个尽可能通用的pipeline脚本样例,让搭建者只需要修改几个赋值参数就可以在自己的项目中应用,初衷是希望所有人能理解pipeline中的过程,但也发现一些比较麻烦的问题,比如有些人不熟悉具体的脚本拿来随意删改导致各种错误,还有就是我们在pipeline脚本中增加一些新功能时又需要通知所有的pipeline维护人员去修改,过程非常纠结。
这时候就意味着我们需要用到pipline的共享库功能(Shared Libraries)了,在各种项目之间共享pipeline核心实现,以减少冗余并保证所有job在构建的时候会调用最新的共享库代码 。
这篇我们就介绍下pipeline的这个黑科技:Shared Libraries

目录结构

Shared Library通过库名称、代码检索方法(如SCM)、代码版本三个要素进行定义,库名称尽量简洁,因为它会在脚本中被调用,在编写 Shared Library的时候,我们需要遵循固定的代码目录结构。
Shared Library代码目录结构如下:

src目录就是标准的Java源目录结构。执行Pipeline时,该目录将添加到类路径中。
vars目录托管定义可从Pipeline访问的全局脚本(一般我们可以在这里编写标准化脚本)。通常,每个.groovy文件的基本名称应使用驼峰(camelCased)模式,.txt(如果存在)可以包含格式化处理的文档。
resources目录允许libraryResource从外部库中使用步骤来加载相关联的非Groovy文件。目前内部库不支持此功能。

定义全局库

这里只介绍全局 Shared Library的方式,通过Manage Jenkins ? Configure System ? Global Pipeline Libraries 的方式可以添加一个或多个共享库。
这些库将全局可用,系统中的任何Pipeline都可以利用这些库中实现的功能。并且通过配置SCM的方式,可以保证在每次构建时获取到指定Shared Library的最新代码。

动态加载库

从2.7版本起,Pipeline: Shared Groovy Libraries plugin插件提供了一个新的参数“library”,用于在脚本中加载(non-implicit)库
如果只需要加载全局变量/函数感兴趣(从vars/目录中),语法非常简单:
此后脚本中可以访问该库中的任何全局变量。

library ‘my-shared-library‘

采用此方式从src/目录中引用类也是可以的,不过只能动态地使用库类(无类型检查),从library步骤的返回值通过指定名称访问它们。比如static可以使用类似Java的语法来调用方法:

library(‘my-shared-library‘).com.mycorp.pipeline.Utils.someStaticMethod()

使用该library步骤时,您还可以指定一个版本,该指定版本将会覆盖默认版本。

library ‘[email protected]‘

Shared Libraries实战

我们在https://testerhome.com/topics/10010已经介绍了一个项目样例,可以看到过程已经非常复杂,让普通业务测试人员管理确实起来确实有点困难。
通过参数化处理后,除了一些各项目的业务变量,整个过程在所有项目都是通用的,完全适合采用共享库的方式进行改造,屏蔽脚本的复杂度。
在改造之前我们勾画了两种思路:pipeline模块库和模版库(姑且这么叫吧)。
1.模块库方式
模块库的方式,其实就是考虑把各个stage的实现通过函数化的方式抽象出来,比如获取代码的stage实现我们就抽象出codeFetch(),单元测试的 stage我们就抽象出unitTest().
特点:业务测试人员负责维护pipeline的初始赋值和整体结构,灵活度高,可自主裁剪stage场景
不足:整体结构还是比较复杂,需要维护的共享脚本比较多,无法对交付流水线过程进行统一管理,Declarative Pipeline只支持script部分脚本的共享库。
pipeline代码样例:

#!groovy
library ‘weiyi-pipeline-library‘
pipeline {
    agent any
    parameters {
    //repoBranch参数
    string(name:‘repoBranch‘, defaultValue: ‘master‘, description: ‘git分支名称‘)
    //服务器选择
    choice(name: ‘server‘,choices:‘192.168.1.107,9090\n192.168.1.60,9090‘, description: ‘测试服务器列表选择(IP,JettyPort,Name,Passwd)‘)
    string(name:‘dubboPort‘, defaultValue: ‘31100‘, description: ‘测试服务器的dubbo服务端口‘)
    //单元测试代码覆盖率要求,各项目视要求调整参数
    string(name:‘lineCoverage‘, defaultValue: ‘20‘, description: ‘单元测试代码覆盖率要求(%),小于此值pipeline将会失败!‘)
    //若勾选在pipelie完成后会邮件通知测试人员进行验收
    booleanParam(name: ‘isCommitQA‘,description: ‘是否在pipeline完成后,邮件通知测试人员进行人工验收‘,defaultValue: false )
    }
    //环境变量,初始确定后一般不需更改
    tools {
        maven ‘maven3‘
        jdk   ‘jdk8‘
    }
    .......
    //pipeline的各个阶段场景
    stages {
        stage(‘代码获取‘) {
            steps {
              codeFetch()
            }
        }
        stage(‘单元测试‘) {
            steps {
              unitTest()
            }
        }
    }
}

共享库代码:

// vars/codeFetch.groovy
def call() { echo "starting fetch code......" }

2.模版库方式
Declarative 1.2(released in late September, 2017),开始支持整条Declarative Pipeline作为共享库,使用条件如下:

Only entire pipeline`s can be defined in shared libraries as of this time. This can only be done in `vars/*.groovy, and only in a callmethod. Only one Declarative Pipeline can be executed in a single build, and if you attempt to execute a second one, your build will fail as a result.

特点:可以将整条declarative pipeline作为共享库让各个项目调用,业务测试人员只需要维护初始化赋值参数即可。
不足:公司技术栈不统一的话,pipeline模版库的适配能力需要比较强(比如可能会出现虚拟机/docker共存,gradle/maven共存等多种情况),可能需要定义多个模版库,不过这些问题通过groovy代码逻辑上应该都可以控制。
pipeline代码样例(敏感信息隐藏):

#!groovy
library ‘weiyi-pipeline-library‘
    def map = [:]
    /*参数化变量,运行时可选择*/
    //git分支名称
    map.put(‘repoBranch‘,‘master‘)
    //测试服务器列表选择(IP,JettyPort,Name,Passwd)
    map.put(‘server‘,‘192.168.1.107,9090\n192.168.1.60,9090‘)
    //测试服务器的dubbo服务端口
    map.put(‘dubboPort‘,‘31100‘)
    //单元测试代码覆盖率要求,各项目视要求调整参数
    map.put(‘lineCoverage‘,‘20‘)

    /*环境变量,初始确定后一般不需更改*/
    map.put(‘maven‘,‘maven3‘)
    map.put(‘jdk‘,‘jdk8‘)

    /*常量参数,初始确定后一般不需更改*/
    map.put("isDocker",false)
    //项目gitlab代码地址
    map.put(‘REPO_URL‘,‘****‘)
    //git服务全系统只读账号,无需修改
    map.put(‘CRED_ID‘,‘****‘)
    //pom.xml的相对路径
    map.put(‘POM_PATH‘,‘pom.xml‘)
    //生成war包的相对路径
    map.put(‘WAR_PATH‘,‘rpc/war/target/*.war‘)
    //测试人员邮箱地址
    map.put(‘QA_EMAIL‘,‘***‘)
    //接口测试job名称
    map.put(‘ITEST_JOBNAME‘,‘Guahao_InterfaceTest_ExpertPatient‘)

pipelineCall("maven",map)

共享库代码:

#!groovy
def call(String type,Map map) {
    if (type == "maven") {
    pipeline {
        agent any
        //参数化变量,目前只支持[booleanParam, choice, credentials, file, text, password, run, string]这几种参数类型,其他高级参数化类型还需等待社区支持
        parameters {
        //固定设置三类pipeline场景
        choice(name:‘scene‘,choices:"scene1:完整流水线\nscene2:代码检查\nscene3:测试部署", description: ‘场景选择,默认运行完整流水线,如果只做开发自测可选择代码检查,如果只做环境部署可选择测试部署‘)
        //repoBranch参数后续替换成git parameter不再依赖手工输入,JENKINS-46451
        string(name:‘repoBranch‘, defaultValue: "${map.repoBranch}", description: ‘git分支名称‘)
        //服务器相关参数采用了组合方式,避免多次选择
        choice(name: ‘server‘,choices:"${map.server}", description: ‘测试服务器列表选择‘)
        string(name:‘dubboPort‘, defaultValue: "${map.dubboPort}", description: ‘测试服务器的dubbo服务端口‘)
        //单元测试代码覆盖率要求,各项目视要求调整参数
        string(name:‘lineCoverage‘, defaultValue: "${map.lineCoverage}", description: ‘单元测试代码覆盖率要求(%),小于此值pipeline将会失败!‘)
        //若勾选在pipelie完成后会邮件通知测试人员进行验收
        booleanParam(name: ‘isCommitQA‘, defaultValue: false, description: ‘是否在pipeline完成后,邮件通知测试人员进行人工验收‘)
        }
        //环境变量,初始确定后一般不需更改
        tools {
            maven "${map.maven}"
            jdk   "${map.jdk}"
        }
        //常量参数,初始确定后一般不需更改
        environment{
            REPO_URL="${map.REPO_URL}"
            //git服务全系统只读账号,无需修改
            CRED_ID="${map.CRED_ID}"
            //pom.xml的相对路径
            POM_PATH="${map.POM_PATH}"
            //生成war包的相对路径
            WAR_PATH="${map.WAR_PATH}"
            //测试人员邮箱地址
            QA_EMAIL="${map.QA_EMAIL}"
            //接口测试job名称
            ITEST_JOBNAME="${map.ITEST_JOBNAME}"
        }

        options {
            disableConcurrentBuilds()
            timeout(time: 1, unit: ‘HOURS‘)
            //保持构建的最大个数
            buildDiscarder(logRotator(numToKeepStr: ‘10‘))
        }
        //pipeline的各个阶段场景
        stages {
            stage(‘代码获取‘) {
                steps {
                //一些初始化操作
                    script {
                    //根据param.server分割获取参数
                    def split=params.server.split(",")
                    serverIP=split[0]
                    jettyPort=split[1]
                    serverName=split[2]
                    serverPasswd=split[3]
                    //场景选择
                    println params.scene
                    //单元测试运行场景
                    isUT=params.scene.contains(‘scene1:完整流水线‘) || params.scene.contains(‘scene2:代码检查‘)
                    println "isUT="+isUT
                    //静态代码检查运行场景
                    isCA=params.scene.contains(‘scene1:完整流水线‘) || params.scene.contains(‘scene2:代码检查‘)
                    println "isCA="+isCA
                    //部署测试环境运行场景
                    isDP=params.scene.contains(‘scene1:完整流水线‘) || params.scene.contains(‘scene3:测试部署‘)
                    println "isDP="+isDP
                    //第三方库安全性检查
                    isDC=params.scene.contains(‘scene1:完整流水线‘)
                    println "isDC="+isDC
                    //接口测试运行场景
                    isIT=params.scene.contains(‘scene1:完整流水线‘)
                    println "isIT="+isIT
                    try{
                        wrap([$class: ‘BuildUser‘]){
                        userEmail="${BUILD_USER_EMAIL},${QA_EMAIL}"
                        user="${BUILD_USER_ID}"
                        }
                   }catch(exc){
                        userEmail="${QA_EMAIL}"
                        user="system"
                    }
                  echo "starting fetchCode from ${REPO_URL}......"
                  // Get some code from a GitHub repository
                  git credentialsId:CRED_ID, url:REPO_URL, branch:params.repoBranch
                 }
                }
            }
            stage(‘单元测试‘) {
                when {
                     expression
                        {return isUT }
                }

            }
................................................................................................以下省略几百行
 else if (type == "gradle"){
   pipeline {
        agent any
................................................................................................继续省略几百行
}

通过这种方式,整个pipeline脚本的实现和复杂度就被封装到Shared Library中。而且如果我需要在原来的流水线基础上增加一个新的stage,比如新增安全测试场景,也只需要库开发人员修改共享库的功能,需要此场景的项目在map赋值时增加相应的赋值参数即可。

后记

Shared Libraries的方式抽象了各种项目之间共享的代码(甚至整条完整的pipeline),有效降低了业务测试人员使用pipeline脚本的复杂度。
同时通过外部源代码控制(SCM)的方式,可以保证最新提供的库代码功能可被所有pipeline项目即时使用,在大团队推广和协作过程中可起到非常重要的作用。

时间: 2024-12-31 22:19:19

[持续交付实践] pipeline:pipeline 使用之 Shared Libraries的相关文章

云端基于Docker的微服务与持续交付实践

云端基于Docker的微服务与持续交付实践笔记,是基于易立老师在阿里巴巴首届在线技术峰会上<云端基于Docker的微服务与持续交付实践>总结而出的. 本次主要讲了什么? Docker Swarm Docker Swarm mode 微服务支持(Docker集群架构体系) Docker的发展趋势和前沿成果 在Docker技术方面还是很佩服大牛的,所以赶紧写下笔记,追随大神的脚步. 阿里云资深专家易立,技术就不说了,他比其他直播间硬生生多讲了半个多点,于情于理还是万分感谢本次分享的(可惜devOp

Docker学习总结(7)——云端基于Docker的微服务与持续交付实践

本文根据[2016 全球运维大会?深圳站]现场演讲嘉宾分享内容整理而成 讲师简介 易立 毕业于北京大学,获得学士学位和硕士学位:目前负责阿里云容器技术相关的产品的研发工作. 加入阿里之前,曾在IBM中国开发中心工作14年,担任资深技术专员,负责IBM企业平台云产品线PureApplication System的研发工作:还负责和参与了一系列IBM在Web 2.0,SOA中间件的研发和创新,也曾为全球客户提供SOA技术咨询和项目实施. 日程 大家好,我演讲的主题是<云端基于Docker的微服务与持

持续交付实践-pipeline 使用之 MultiBranch Pipeline

前言 在探讨multiBranch Pipeline之前,很有必要先探讨下如何制定有效的代码分支管理规范,使用高效的版本控制系统,并对构建产物及其依赖进行管理.我们首先要强调,需要进行版本控制的不仅是源代码,还有测试代码.数据库脚本.构建和部署脚本.依赖的库文件等,并且对构建产物的版本控制也同样重要.只有这些内容都纳入版本控制了,才能够确保所有的开发.测试.运维活动能够正常开展,系统能够被完整的搭建.制定有效的分支管理策略对达成持续交付的目标非常重要.看过<持续交付>这本书的同学都知道,持续交

[持续交付实践] pipeline:pipeline 使用之语法详解

一.引言 jenkins pipeline语法的发展如此之快用日新月异来形容也不为过,而目前国内对jenkins pipeline关注的人还非常少,相关的文章更是稀少,唯一看到w3c有篇相关的估计是直接翻译软件翻的,读下来惨不忍睹.没办法,语法详解这章我干脆把jenkins官网上的语法说明全部翻译了一遍,并更新了陈旧的内容(可怜了我大学四级的英语水平~),英语好的朋友也可以直接到官网阅读. 二.语法简介 Pipeline最基本的部分是"step".基本上,step告诉Jenkins 要

[持续交付实践] 安全扫描自动化测试平台实现

前言 TesterHome有人专门加了我QQ问安全测试这个话题,所以这篇准备先聊聊持续交付中的安全测试.现在信息安全已经上升到了国家战略的高度,特别是今年<中华人民共和国网络安全法>颁布后,用户隐私通过国家立法的方式被严格要求保护,另外一方面安全灰产行业风起云涌,形成了一个巨大的地下产业链条和破坏能力.在此背景下,越来越多的互联网公司也开始组建自己的安全职能部门,但也会发现很多公司的软件并没有经过专门的安全测试便运行在互联网上,它们携带着各类安全漏洞直接暴露在公众面前,其中一些漏洞甚至直指软件

联想企业网盘:SaaS服务集群化持续交付实践

1      前言 当代信息技术飞速发展,软件和系统的代码规模都变得越来越大,而且组件众多,依赖繁复,每次新版本的发布都仿佛是乘坐一次无座的绿皮车长途夜行,疲惫不堪.软件交付是一个复杂的工程,涉及到软件开发的各个细节,其中任何一环出现问题,都会导致软件不能及时交付,或者交付的质量堪忧. 从企业的角度来讲,如何利用更科学的工具.更科学的流程来提高产品质量,提升客户满意度,是刚需.从员工角度来讲,生命里值得追求的事情很多,不能把宝贵的时间浪费在一些机械的.重复的事情上面. 联想企业网盘从2007开始

[持续交付实践] 基于 sonarqube 的代码检查平台实现

前言 公司此前用的一直是的SonarQube5.1(2015年版本,为兼容jdk6和jdk7的项目一直没有升级),最近为了pipeline的集成刚刚升级到了最新的SonarQube6.5版本.网上对SonarQube6的介绍比较少,这里重点先介绍下SonarQube6以后的一些新增特性.1.代码问题重新分级,将问题分为bug.漏洞.坏味道:将代码检查结果从可靠性.安全性.可维护性几个角度进行问题分类和风险分级.2.更丰富的代码检查规则,更友好的问题处理曲线展示,更清晰的质量阈和代码规则定制.3.

[持续交付实践] pipelne:pipeline 使用之项目样例

项目说明 本文将以一个微服务项目的具体pipeline样例进行脚本编写说明.一条完整的pipeline交付流水线通常会包括代码获取.单元测试.静态检查.打包部署.接口层测试.UI层测试.性能专项测试(可能还有安全.APP等专项).人工验收等研发测试环节,还会包括灰度发布.正式发布等发布环节. 补充说明:1.此项目的部署还是使用传统虚拟机服务器的方式,暂未采用docker容器,docker容器与pipeline的结合后面会有其他专题进行说明.2.灰度发布.正式发布等发布环节,由于涉及到线上发布系统

[持续交付实践] pipeline:pipeline 使用之快速入门

什么是pipeline 先介绍下什么是Jenkins 2.0,Jenkins 2.0的精髓是Pipeline as Code,是帮助Jenkins实现CI到CD转变的重要角色.什么是Pipeline,简单来说,就是一套运行于Jenkins上的工作流框架,将原本独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂发布流程.Pipeline的实现方式是一套Groovy DSL,任何发布流程都可以表述为一段Groovy脚本,并且Jenkins支持从代码库直接读取脚本,从而实现了Pipe