# 如何基于Jenkins共享库编写插件系统? 最近在研究 **Harbor** 服务部署方式时,不小心点看到了它的 **[jenkins-shared-library](https://github.com/goharbor/jenkins-shared-library "Harbor Jenkins Shared Library")** 项目,其中下面这张图里这两句给了我灵感 ![](https://files.mdnice.com/user/23272/e4790158-c84c-4e9c-b659-0a54f93c7092.png) 第一次发现这个 **[writeFile](https://www.jenkins.io/doc/pipeline/steps/workflow-basic-steps "Basic Steps")** **step** 可以将包文件写入工作空间,随即查看了其文档,截图如下: ![](https://files.mdnice.com/user/23272/57f1be1a-b187-417d-bd55-cf09f0a196cf.png) 今天就分享一个基于 **Jenkins** 共享库「**[Jenkins Shared Library](https://www.jenkins.io/zh/doc/book/pipeline/shared-libraries/ "Jenkins Shared Library")**」如何写一个插件系统。 ## 背景介绍 几年前有幸了解到一个叫 **Wercker** 的 CI/CD 系统,通过 **Shell** 扩展其构建能力,并且在 **GitHub** 上也有很多开源 Wercker Step,比如:**[step-kubectl](https://github.com/wercker/step-kubectl "step-kubectl")**、**[step-maven](https://github.com/wercker/step-maven "step-maven")**。但是从 **Oracle** 花 $4 亿美元收购后,这项目就没咋个更新,所以一直在想能否在 **Jenkins** 中复用这些 **step** 代码呢? 现有的 **Jenkins** 虽然有大量可用的插件,但是满足自己场景的插件不多,而且在实践过程中会有很多定制场景,如果基于 **Jenkins** 现有的扩展方式开发插件,成本会很高,并且插件安装过多,容易导致内存暴涨,引起插件冲突等各种问题。 说了这么多其实就是来需求了不想写插件。 ## 解决方案 在 **Jenkins** 中复用 Wercker Step 的功能,方法其实挺简单。 第一,通过 **writeFile** 将 **step** 包写入 **job** 工作空间 第二,将 **step** 配置参数,转化为环境变量,通过 **withEnv** 和 **withCredentials** 注入 第三,在 **job** 工作空间解压 step 包,并执行 **run.sh** 入口文件 ## 实现细节 这里具体分析一下 **wercker-shared-library** 实现细节 第一,step 定义 定义 **wsl_step** 关键字(也可以叫 **step**),包含一个 step 名称和 step 参数,step 参数格式为 **yaml** 格式字符串。 ```groovy //file: vars/wsl_step.groovy def call(String stepName, String stepOpts = null){ ``` 下面是使用样例 ```groovy wsl_step('sample', """ vars: docker-run-image: k8ops/jenkins-build:maven-java8u201 secrets: docker-auth: usernamePassword/acr-image-auth """) ``` 第二,下载 **wercker step** 包到 **job** 工作空间 ```groovy writeFile file: ".wercker/${stepName}.tar.gz", text: libraryResource(resource: "steps/${stepName}.tar.gz", encoding: "Base64"), encoding: "Base64" ``` 第三,处理 step 参数,将其转化为环境变量,这个过程是最复杂的,为了方便使用 **jenkins credentials**,配置格式上将其扁平处理。 下面配置方式就是使用 **jenkins credentials** 方法, ```yaml secrets: docker-auth: usernamePassword/acr-image-auth ``` 将上面的这些信息,进行文本处理转化为 **Jenkins** 能识别的格式,下面是转化代码。 ```groovy // usernamePassword/xxx-id def values = it.value.split("/") def type = values[0] def credId = values[1] if (type == "usernamePassword") { secrets << usernamePassword(credentialsId: credId, usernameVariable: "${secretEnvPrefix}USR", passwordVariable: "${secretEnvPrefix}PSW") } else if (type == "sshUserPrivateKey") { secrets << sshUserPrivateKey(credentialsId: credId, keyFileVariable: "${secretEnvPrefix}KEYFILE", passphraseVariable: "${secretEnvPrefix}KEYPASSWORD", usernameVariable: "${secretEnvPrefix}USER") } else if (s.type == "FileBinding") { secrets << [ $class: 'FileBinding', credentialsId: credId, variable: "${envPrefix}${stepKey}_${secretKey}" ] } else { error "${type} is not support" } withCredentials(secrets) { sh "echo ${XXX_USR}" } ``` 普通参数就好处理,直接转化为环境变量即可,下面是转化代码。 ```groovy def envPrefix = "WERCKER_" def stepKey = stepName.replace("-", "_").toUpperCase() Map environment = [:] if (opts.vars instanceof Map) { opts.vars.each { def varKey = it.key.replace("-", "_").toUpperCase() environment["${envPrefix}${stepKey}_${varKey}"] = it.value } } ``` 最终产生的环境变量格式如下: ```yaml vars: docker-run-image: k8ops/jenkins-build:maven-java8u201 --- WERCKER_SAMPLE_DOCKER_RUN_IMAGE=k8ops/jenkins-build:maven-java8u201 secrets: docker-auth: usernamePassword/acr-image-auth --- WERCKER_SAMPLE_DOCKER_AUTH_USR WERCKER_SAMPLE_DOCKER_AUTH_PSW ``` `WERCKER_` 是前缀,`SAMPLE`是 step 名称。 第四,解压 step 包并执行 ```groovy List stepEnvVars = [] environment?.each { key, value -> stepEnvVars << "$key=$value" } withEnv(stepEnvVars) { withCredentials(secrets) { sh """ rm -rf .wercker/${stepName} mkdir -p .wercker/${stepName} tar --strip-components=1 -xzf .wercker/${stepName}.tar.gz -C .wercker/${stepName} chmod 755 .wercker/${stepName}/run.sh bash .wercker/${stepName}/run.sh """ } } ``` 第五,下载一个 wercker step 打包到 `resources/steps` 目录 样例里面的 step 包叫 `sample` ![](https://files.mdnice.com/user/23272/f5d45ca8-cca3-42fe-9d88-a451450eb9fb.png) ## 如何使用 第一,环境准备 由于网络环境问题,演示代码存放在 Gitee 上,所以需要依赖 **[pipeline-gitee-lib-plugin](https://github.com/seanly/pipeline-gitee-lib-plugin "pipeline-gitee-lib-plugin")** 插件,因为这个插件是临时改的,没有产物包,需要的自己编译(其实我很奇怪为啥国内没有这种插件,只有GitHub)。 ![](https://files.mdnice.com/user/23272/571a5806-9e7e-4ac8-a805-62ba6b885cdf.png) ![](https://files.mdnice.com/user/23272/3b1588b3-9e73-4628-986b-b2af710d43fe.png) 第二,新建测试任务 ![](https://files.mdnice.com/user/23272/b9bdfbbe-c986-4f07-8f21-85d0ab8f73a7.png) 将下面的代码拷贝到 job 配置中 ![](https://files.mdnice.com/user/23272/1398e15d-592d-4d5f-8a68-56c61a979356.png) ```groovy //vim: ft=groovy @Library ("gitee.com/opsbox-ecosystem/wercker-shared-library@master") _ node () { catchError { properties([ buildDiscarder(strategy: logRotator(numToKeepStr: "15")) ]) stage('Test') { wsl_step("sample", """ vars: whoami: seanly docker-reg: registry.cn-hangzhou.aliyuncs.com secrets: docker-auth: usernamePassword/acr-docker-auth """) wsl_step("sample", """ vars: whoami: jenkins """) } } } ``` 第三,触发构建 ![](https://files.mdnice.com/user/23272/964764c7-229c-4f9d-bd46-b2999a2fa9d3.png) ![](https://files.mdnice.com/user/23272/6aff9a4d-b94a-43ee-9b87-c351bff45212.png) ## 最后 这套代码已经开源 **[wercker-shared-library](https://github.com/opsbox-ecosystem/wercker-shared-library "Wercker Shared Library")**,对此感兴趣的可以了解一下。 虽然这套插件机制方法简单,但是插件机制带来的是丰富的想象力。