笔者是独立翻译实验,不允许转载
Extending with Shared Libraries
Defining Shared Libraries
Pipeline被越来越多的组织和项目所使用,一些常用的模式开始出现。通常共享不同项目的Pipeline片段是非常有用的,能减少冗余,保持代码DRY(Don’t_repeat_yourself)。
Pipeline支持创建共享库,这些库能被外部源代码库定义,并能被装载到现有的Pipeline中。
Directory structure
共享库被一个名字,一个例如SCM的代码段定义,默认的版本是可选的。名字是一个短标识符,它能被用在脚本中。
版本可以是SCM能理解的任何东西;例如分支,标记和提交hashes等所有Git的东西你同样可以申明哪些脚本明确要求下面定义的库,或者默认是禁止的。更进一步,如果你在Jenkins配置中说明一个版本,你就能阻止脚本选择一个不同的版本。
说明SCM最好的方式就是使用SCM插件,插件已经被更新支持新的API,检查任意的命名版本。最新版本的Git和Subversion插件都支持这个模式;其他也应该这么做。
如果你的SCM插件没有被整合,你可以选择Legacy SCM,并且挑选已经提供的。在本例中,你需要在SCM配置的某个地方包含(include)${library.yourLibName.version},以便于在迁出代码阶段,插件能读取这个变量选择想要的版本。例如,对于Subversion,你能设置URI为svnserver/project/${library.yourLibName.version},然后使用例如trunk或者branches/dev或者tags/1.0。
目录结构(Directory structure)
共享库的目录结构如下:
src目录看起来像是标准的java代码目录。当执行Pipeline的时候,这个目录会被加到classpath。
vars目录保存脚本,脚本定义Pipeline可以存取的全局变量。每一个groovy的基本名应该是一个Groovy标识符,方便起见都是驼峰命名法。
这些目录中的Groovy原文件都与脚本Pipeline同样的”CPS transformation”。
resources目录允许libraryResource被外部库使用加载非Groovy文件。现在这个特性不支持内部库。
Global Shared Libraries
有好几个可以定义共享库的地方,取决于用户情况。Manage Jenkins » Configure System » Global Pipeline Libraries,在这里许多库都能被定义。
因为这些库是全局使用,系统中的任何Pipeline都可以用这些库中的功能。
这些库被认为是”可信任的”,他们能运行Java/Geoovy/Jenkins内部API/Jenkins插件或者第三方库的任何方法。这允许你以一个更高级别封装去包裹独立的不安全的APIs,供其他Pipeline使用。要知道,任何能提交文件到这个SCM库的人都能对Jenkins进行无限的存取。你需要全部/运行权限去配置库(通常权限被授予jenkins管理员)。
Folder-level Shared Libraries
任何创建的目录都可以有和它相关联的共享库。这个机制允许特殊库对目录和子目录中的Pipeline作用范围。
目录为基础的库并不被认为是”可信任的”;它们在Groovy sandbox中的运行就像典型的Pipeline一样。
Automatic Shared libraries
其他插件可以增加定义库的方法。例如插件github-branch-source提供一个GitHub组织目录项,这个目录项允许脚本不用任何附加配置就可以使用不受信任的库,例如github.com/someorg/somerepo。本例中,指定的GitHub库将被加载。
Using libraries
隐式加载的共享库允许Pipeline使用任意这样的库定义的类或者全局变量。存取其他的库,Jenkinsfile需要使用@Library标记,说明库的名字:
这些标记可以在Groovy脚本允许的任何地方。当引用类库(src目录)的时候,相应标记需要一个import表达式:
提示:对于只定义了全局变量的共享库,或者只需要一个全局变量的共享库,<a href="http://groovy-lang.org/objectorientation.html#<em>annotation">annotation</a> pattern <code>@Library('my-shared-library') _</code>
保持代码精炼是有用的。本质上,替代标记一个不必要的import
表达式,符号被标记。
不推荐import一个全局变量/函数,因为这将强迫编译器将字段和方法解释为静态(static)的。此种情况下的Groovy编译器会产生令人迷惑的错误信息。
在脚本的编译过程中, 库在开始执行之前被解析和加载。这使得 Groovy 编译器能够理解静态类型检查中使用的符号的含义, 并允许它们在脚本中的类型声明中使用, 例如
但是, 全局变量在运行时解析。
Loading libraries dynamically
从Pipeline2.7 版: 共享的 Groovy 库插件中, 有一个新的选项用于在脚本中加载 (non-implicit) 库: 在生成过程中的任何时候动态加载库的一个library步骤。
如果你只对使用全局变脸函数感兴趣(从vars目录),语法是相当的简单:
此后, 脚本将可以访问该库中的任何全局变量。
也可以使用src/目录中的类, 但更棘手。虽然 @Library 注释在编译之前已经准备脚本的classpath, 但在遇到library step时, 脚本已经编译完毕。因此, 您无法从库中import或以其他方式,静态引用类型。
但是, 您可以动态地使用库类 (没有类型检查), 通过完全访问来自library step的返回值的限定名。可以使用类似于 Java 的语法调用静态static方法:
您还可以访问静态字段, 并调用构造函数, 就好像它们是名为new的静态方法一样:
Library versions
当隐式加载被选中的时候,共享库使用默认版本,否则pipeline只通过名字引用库,例如@Library(‘my-shared-library’)。如果默认版本没有定义,Pipeline必须说明一个版本,例如@Library(‘my-shared-library@master’)。
如果共享库配置中的”允许默认版本被覆盖”被选中,@Library标记有可能覆盖库的默认版本。如果可能的话,这同样允许隐式加载的库能被加载不同的版本。
当时使用library step,你可以说明一个版本:
由于这是一个常规步骤(library step), 因此可以计算该版本而不是常量, 就像注释;例如:
将使用与 multibranch Jenkinsfile 相同的 SCM 分支加载库。另外一个例子中,你可以通过参数选择一个库:
注意:Library step可能不可以被用来覆盖一个隐式加载库的版本。它在脚本开始的时候已经加载,并且一个用名字加载的库不能被加载两次。
Retrieval Method
说明SCM最好的方式是使用SCM插件,插件已经被更新支持新的API去检查版本。在写本文时,Git和Subversion插件已经支持这个模式。
Legacy SCM
SCM插件有可能仍然通过Legacy SCM选项使用,因为插件还没有支持交心的共享库特性。本例中,包含${library.yourlibrarynamehere.version},在这里分支/tag/ref为了特殊的SCM插件被配置。这确保,在迁出库的源代码时,SCM插件能扩展它的变量去迁出合适的库版本。
Dynamic retrieval
如果你在Library step中仅仅指明一个库名字(可选的,在version后面@) ,Jenkins将寻找那个名字的预先配置的库。(或者加载一个github.com/owner/repo自动库)
但您也可以动态地指定检索方法, 在这种情况下, 不需要在Jenkins中预先定义库。例子如下:
最好使用Pipeline Syntax寻找你的SCM的精确的语法。
注意:在这些cases中,必须要说明库版本。
Writing libraries
一般来说,任何合法的Groovy代码都可以使用。不同的数据结构,功能方法等,例如:
Access steps
库的类不能明确的调用steps,例如sh或者git。但是, 它们可以在封闭类的范围之外实现方法, 进而调用Pipeline step,例如:
然后这个方法可以被脚本式Pipeline调用。
这个方法是有限制的,例如,它阻止一个父类的定义。
或者,在构造函数中,一套steps集合能用this被明确的传递到一个库类,或者仅仅一个方法:
当保存类的状态的时候,例如上面,类必须实现Serializable接口。
这确保在Jenkins中,一个使用类的Pipeline,像下面的例子,能挂起(suspend)和回复(resume)。
如果库需要存取全局变量,例如env,那么这些库应该被明确的传递到库类,或者方法中。
而不是将大量的变量从脚本式Pipeline传递到库中,
上面的例子展示了参数被传递到一个静态(static)方法,脚本式Pipeline按照如下方式调用:
Defining global variables
在内部,vars目录中的脚本按要求实例化为单例。这允许在单个. groovy 文件中定义多个方法以方便使用。
声明式Pipeline不允许在脚本指令之外使用全局变量用法 (JENKINS-42360)。
① script在声明式Pipeline中访问全局变量所需的脚本指令。
注意:在Jenkins加载并使用该库作为成功的Pipeline运行的一部分之后, 在共享库中定义的变量将只显示在全局变量引用 (在Pipeline Syntax中)。
警告:
避免在全局变量中保留状态
避免使用交互或保留状态的方法来定义全局变量。改用静态类或实例化类的局部变量。
Defining custom steps
共享库还可以定义与内置步骤 (如 sh 或 gitgit) 类似的全局变量。在共享库中定义的全局变量必须用所有 lower-case 或法则命名, 以便通过管道正确加载。
例如, 要定义 sayHello, 应创建文件 vars/sayHello.groovy, 并应实现调用方法。调用方法允许以类似于步骤的方式调用全局变量:
Pipeline能引用和调用这个变量:
如果用块调用, 则调用方法将接收关闭。应显式定义该类型以阐明步骤的意图, 例如:
然后, Pipeline可以像任何接受块的内置步骤一样使用此变量:
Defining a more structured DSL
如果您有很多类似的管道, 则全局变量机制提供了一个方便的工具来构建一个 higher-level 的 DSL 来捕获相似性。例如, 所有的詹金斯插件都是以同样的方式构建和测试的, 所以我们可以写一个名为buildPlugin的step。
假设脚本已作为全局共享库或文件夹级共享库加载, 结果 Jenkinsfile 将大大简化:
Using third-party libraries
可以使用 third-party 的 Java 库, 通常是在 Maven 中心, 从受信任的库代码使用@Grab注释。有关细节, 请参阅Grape document, 但简单地把:
默认情况下, 第三方库被缓存在~/. groovy/grapes/Jenkins的master节点上。
Loading resources
外部库可以使用libraryResource step从resources/目录加载辅助文件。该参数是一个相对路径名, 类似于 Java 资源加载:。
该文件以字符串形式加载, 适合传递给某些 api 或使用 writeFile 保存到工作区。
最好使用一个唯一的包结构, 这样您就不会意外地与另一个库发生冲突。
Pretesting library changes
如果您注意到在使用不受信任的库的生成中出现错误, 只需单击重播链接以尝试编辑其一个或多个源文件, 并查看所产生的生成是否按预期的方式工作。一旦你对结果满意,跟随Build状态页上的不同链接,应用不同的库变化,并保存到代码库中。
(即使为库请求的版本是一个分支, 而不是像标记这样的固定版本, 重播的生成将使用与原始生成完全相同的修订: 将不会再次签出库源。
受信任的库当前不支持重播。在重播过程中, 当前也不支持修改资源文件。
Defining Declarative Pipeline
从2017年9月下旬发布的声明性1.2 开始, 可以定义声明性管道。也可在共享库中进行。下面是一个示例, 它将执行不同的声明管道, 具体取决于内部版本号是奇数还是偶数:
此时, 只能在共享库中定义整个Pipeline。这只能在”vars/*.groovy”,仅仅只能在一个方法调用中。在单个生成中只能执行一个声明性Pipeline, 如果尝试执行第二个Pipeline, 则生成结果将失败。