【问题标题】:Load Jenkins Pipeline Shared Library from same repository从同一存储库加载 Jenkins Pipeline 共享库
【发布时间】:2018-02-23 03:09:24
【问题描述】:

TL;DR 有没有办法将代码导入到Jenkinsfile 从本地 存储库(load 步骤除外)?

为什么?

我已经体验到,对于复杂的构建,Jenkinsfile 会变得有点笨重且不太易于维护。
既然构建工作是代码,那么拥有与其他代码相同的方法会很棒。 也就是说,我想把它分成更小(更易于维护)的单元并单元测试它们。

我尝试了什么

  • shared libraries:允许将我们的 Jenkins 流水线逻辑划分为单独模块中的较小文件,甚至对其进行单元测试。
    但是,它们需要位于不同的存储库中,并且(如果不在 GitHub 上)必须配置到 Jenkins 中。
  • load Step:允许从存储库加载 groovy 脚本。
    但是,这些文件必须是脚本而不是“完整”的 groovy 类,因此很难拥有多个相互依赖的文件或类。例如继承是不可能的。
    此外,这些文件在 Jenkins 作业上执行 replay 时不会显示,这使得它们难以开发和调试。

我的问题

  • 有没有办法(或解决方法)在与Jenkinsfile 相同的存储库中创建共享库并将此库导入Jenkinsfile?
  • 或者还有其他我还没有尝试过的方法吗?

示例目录结构

类似于为shared libs 描述的目录结构我希望在单个存储库中拥有以下内容。

(root)
+- someModule
|   +- ...
+- jenkins           # Classes/Scripts used by Jenkins in a separate module
|   +- src                       # Groovy source files
|      +- org
|          +- foo
|              +- Bar.groovy     # for org.foo.Bar class
|   +- test                      # Groovy test files
|      +- org
|          +- foo
|              +- BarTest.groovy # Test for org.foo.Bar class
|   +- pom.xml or build.groovy   # Build for local library
+- Jenkinsfile     # Build "someModule", uses classes from "jenkins" module

【问题讨论】:

  • "共享库...需要在不同的存储库中,并且(如果不在 GitHub 上)必须配置到 Jenkins 中。"为什么这是个问题?共享库是跨管道重用代码的标准方式,您可以拥有纯 groovy 代码,用 Spock 对其进行测试,它对我很有效。
  • @Boris 我的用例实际上不是在项目之间共享它,而是将一个Jenkinfile 分成更小的(更易于维护)的单元并在在一个项目中对它们进行单元测试 ,即Jenkinsfile的“下一个”。
  • 如果你愿意,你可以将你的 Jenkinsfile 和你的共享库放在同一个 repo 中,然后导入它。我已经这样做了,您仍然需要在 Jenkins 中配置共享库(我也不明白为什么会出现问题)。
  • @Boris 配置共享库只是一个不必要的配置步骤。不是每个人都对他的 Jenkins 实例拥有管理权限。另见icoanel's answer
  • 另一种情况是当您想在不污染公共和共享存储库的情况下测试此功能。

标签: jenkins groovy shared-libraries jenkins-pipeline


【解决方案1】:

解决方法:

library identifier: 'shared-library@version', retriever: legacySCM(scm)

目前在PR 37 中采用的方法无法与构建代理一起正常工作,而且无论如何只能用于使用library 步骤的脚本,而不是@Library 注释。

顺便说一下,从load 步骤加载的文件做会出现在重播中。但是您的脚本确实不能静态引用此类文件中定义的类型。换句话说,您可以模拟库 vars/*.groovy 但不能模拟 src/**/*.groovy — 与当前 PR 37 的限制相同。

【讨论】:

  • 我可以确认这适用于共享库存储库(不在子目录中)。请注意,我们必须将库版本作为identifier 的一部分传递。示例:github.com/cloudogu/ces-build-lib/blob/… 关于如何使用当前 repo 的子目录的任何想法,例如jenkins 在问题中显示的文件夹结构中?
  • 在JENKINS-46721中跟踪子目录。
  • 也仅供参考:workflow-cps-global-lib PR 43
  • 如果库通常是隐式加载的,有什么想法吗?使用相同名称加载不起作用,因为库 can only be loaded once,使用不同名称加载它不起作用 work either。
  • 也许有一种方法可以禁用为特定作业加载隐式库。或者禁用为特定作业加载特定的隐式加载库。
【解决方案2】:

我想正确的方法是实现自定义SCMRetriever。

但是,您可以使用以下 hack:

假设您的本地仓库中的 jenkins/vars/log.groovy 包含:

def info(message) {
    echo "INFO: ${message}"
}

您的Jenkinsfile 可以使用library 步骤从jenkins/ 目录加载该共享库:

node('node1') { // load library
    checkout scm
    // create new git repo inside jenkins subdirectory
    sh('cd jenkins && git init && git add --all . && git commit -m init &> /dev/null') 
    def repoPath = sh(returnStdout: true, script: 'pwd').trim() + "/jenkins"
    library identifier: 'local-lib@master', retriever: modernSCM([$class: 'GitSCMSource', remote: repoPath])
}

node('node2') {
    stage('Build') {
        log.info("called shared lib") // use the loaded library
    }
}

【讨论】:

  • 嗨@Pawel,谢谢!这看起来很有希望。但是,我收到一条错误消息:stderr: fatal: '/home/jenkins/workspace/.../localLibrary' does not appear to be a git repository fatal: Could not read from remote repository。有什么想法吗?
  • @schnatterer,这个命令隐藏了错误。删除&>/dev/null,以便您可以调试阻止其工作的问题:sh('cd jenkins && git init && git add --all . && git commit -m init &> /dev/null')。我怀疑您没有在sh 命令中更改子目录的路径,因此从未初始化过 git repo
  • @Brandon 谢谢。您的提示帮助我修复了sh 步骤中的错误。我需要git config user.email。不幸的是,构建在library 步骤中失败,与我的第一条评论(does not appear to be a git repository)中的错误相同。这是我尝试过的:github.com/schnatterer/jenkins-load-local-lib/blob/…
  • 您的回答启发我写了一篇博客,更深入地解释了这个解决方案,并涵盖了一些陷阱。 code-held.com/2020/01/22/jenkins-local-shared-library我还把解决方案抽象成一个共享库供其他人使用:github.com/mld-ger/jenkins-library-subfolder-loader
  • @schnatterer。你知道问题是什么吗?我有同样的问题hudson.plugins.git.GitException: Command "git ls-remote -- /home/jenkins/workspace/.../library" returned status code 128: stdout: stderr: fatal: '/home/jenkins/workspace/.../library' does not appear to be a git repository fatal: Could not read from remote repository.
【解决方案3】:

你可以看看我写的插件,它可以使用你的管道作为共享库的 repo 的子目录:https://github.com/karolgil/SharedLibrary

构建和安装后,您可以简单地将以下内容放入管道中:

@SharedLibrary('dir/in/repo') _

开始使用 dir/in/repo 作为管道的共享库。

【讨论】:

    【解决方案4】:

    想要做同样的事情并最终创建了这个:

    https://github.com/jenkinsci/workflow-cps-global-lib-plugin/pull/37

    这是我的使用方法:

    https://github.com/syndesisio/syndesis-pipeline-library/blob/master/Jenkinsfile#L3

    在我的例子中,我想创建一个 Jenkinsfile,它实际上是 tests 存储库包含的管道库。

    让我知道您的想法,并随时将您的 cmets 添加到 PR 上。

    【讨论】:

    • 谢谢。看起来正是我正在寻找的东西!如果他们已经硬合并并发布了它:-) 我将我的用例添加到 PR,也许它可以帮助提升它。
    • 关于测试共享库,还有其他方法可以做到这一点,不需要推送到正在运行的 Jenkins。我认为此功能也适用于其他用例。
    【解决方案5】:

    如果您从 SCM 检查管道(未嵌入在 Je​​nkins 作业 UI 配置中),我发现 Pawel 的版本存在问题。这是我针对这些情况的版本:

    node {
        def scriptFolder = sh(script: "echo \$(pwd | sed 's,\\(.*\\)\\(@[0-9]*\$\\),\\1,')@script/\$(sed -n '2,\$p' ../config.xml | xmllint --xpath '(//scriptPath/text())' - | xargs dirname)", returnStdout: true).trim()
        sh("cd ${scriptFolder} && ls -l vars/ && if [ -d .git ]; then rm -rf .git; fi; git init && git add --all . && git commit -m init &> /dev/null")
        library identifier: 'local-lib@master', retriever: modernSCM([$class: 'GitSCMSource', remote: scriptFolder])
    
        stage('Build') {
            log.info("called shared lib") // use the loaded library
        }
    }
    

    对于这些情况,管道本身在与管道本身定义的不同的工作空间(相同的目录名称但名称中包含 @script)中签出。

    【讨论】:

      【解决方案6】:

      我已经成功测试了一个简单的解决方法,用于具有两种协议的 GIT 存储库 - HTTPS 和 SSH(我正在使用 BitBucket) - 只需按如下方式配置 Jenkins 作业(指向同一个存储库但强制使用不同的获取方法):

      • 在分支源中添加“通过 SSH 结帐”选项

      • 在管道库 -> 源代码管理 -> 项目存储库中使用 HTTPS 协议 - 例如像 https://...

      【讨论】:

        【解决方案7】:

        有没有办法将代码从本地存储库导入到 Jenkinsfile 中(加载步骤除外)?

        有没有办法(或解决方法)在与 Jenkinsfile 相同的存储库中创建共享库并将此库导入 Jenkinsfile?

        是的。如果按照规范遵守“共享库存储库的目录结构”,则绝对可行,无需任何变通方法即可使其正常工作。基本上,您的目录结构需要按照以下几行进行调整:

        +- src                     # Groovy source files
        |   +- org
        |       +- foo
        |           +- Bar.groovy  # for org.foo.Bar class
        +- vars
        |   +- foo.groovy          # for global 'foo' variable
        |   +- foo.txt             # help for 'foo' variable
        +- resources               # resource files (external libraries only)
        |   +- org
        |       +- foo
        |           +- bar.json    # static helper data for org.foo.Bar
        +- someModule
        |   +- ...
        |
        |
        +- Jenkinsfile
        

        此答案并非基于猜想。虽然没有记录,但我已经在多个项目和培训中应用了这种模式,并且它有效。无论如何,您都不需要像往常一样修改您的工作配置。

        【讨论】:

        • 我观察到的是:1)如果 Jenkinfile 是用“声明性管道”语法编写的,那么只要对 foo.echo() 方法的调用包含在 "脚本 { ... }”。 2)如果 Jenkinsfile 是用“脚本化管道”语法编写的,那么我会得到一个“没有这样的属性:foo”错误给我的印象是你在这种情况下使用的机制只适用于前者,不是后者。唉,我正在使用的代码库现在还停留在脚本管道语法中。
        • 我终于试了一下,结果是unable to resolve class com.cloudogu.ces.cesbuildlib.Git for github.com/cloudogu/ces-build-lib。我必须在 Jenkinsfile 中加载“库”吗?
        • 这取决于您在设置共享库期间是否选择了“隐式加载”选项。普通的,显式加载库是个好办法:@Library( 'lib-name-as-defined-during-setup' ) _.
        猜你喜欢
        • 2021-10-12
        • 1970-01-01
        • 1970-01-01
        • 2017-04-30
        • 2019-04-17
        • 1970-01-01
        • 2018-01-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多