---
# System prepended metadata

title: 如何基于Jenkins共享库编写插件系统？

---

# 如何基于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")**，对此感兴趣的可以了解一下。

虽然这套插件机制方法简单，但是插件机制带来的是丰富的想象力。

