获取Jenkins Pipeline中的步骤ID,以链接到BlueOcean或Pipeline步骤视图(flowGraphTable)

nxowjjhe  于 2023-01-29  发布在  Jenkins
关注(0)|答案(5)|浏览(365)

假设Jenkins管道运行一系列步骤,其中一些步骤在parallel块内,是否有任何方法可以获得 * 管道内 * 给定步骤或最近步骤的Flow id?
什么是流ID?如果查看Pipeline作业的运行,您可以看到指向flowGraphTable/的“Pipeline Steps”链接。其中包含指向特定作业步骤(如execution/node/113/)的链接。这些链接似乎表示FlowNode
有没有办法从管道内部获取这些ID,以生成链接等?
特别是,我想获得一个到并行分支的子流的链接,这样我就可以链接到它们的BlueOcean视图。(内置的Jenkins视图是无用的,因为它不显示子树)。
我可以看到BlueOcean链接对应于/execution/链接,它们具有相同的id值,如果我的管道分支是myjob/9/execution/node/78/,那么在blueOcean上它将是jobname/9/pipeline/78
但是,如果我想使用构建摘要插件或类似的插件来生成链接并将它们添加到构建结果页面,那么我如何获得这个ID呢?

2w2cym1i

2w2cym1i1#

我也曾在类似的用例中苦苦挣扎,最终找到了一个适合我的解决方案。这本https://issues.jenkins-ci.org/browse/JENKINS-28119可能是一本有趣的读物,它最终为我指明了一个好的方向。
除了管道和管道阶段视图插件,我还必须在Jenkins服务器上安装HTTP请求插件(https://wiki.jenkins.io/display/JENKINS/HTTP+Request+Plugin)和管道实用程序步骤插件(用于解析JSON,https://wiki.jenkins.io/display/JENKINS/Pipeline+Utility+Steps+Plugin)。
下面是我的工作示例,只是缺少正在评估的阶段:

#!groovy

pipeline {
    agent any

    stages {
        stage('Test') {
            steps {
                script {
                    def responseRun = httpRequest(
                        //consoleLogResponseBody: true,
                        contentType: 'APPLICATION_JSON',
                        httpMode: 'GET',
                        url: BUILD_URL + 'wfapi',
                        validResponseCodes: '200'
                    )
                    def runJson = readJSON text: responseRun.getContent()
                    def headNodeUrl = ''
                    runJson.stages.each {
                        if (it.name.toString() == 'Stage node label') {
                            // Found head node: it.id
                            headNodeUrl = BUILD_URL + 'execution/node/' + it.id.toString() + '/'
                        }
                    }
                    def responseNode = httpRequest(
                        contentType: 'APPLICATION_JSON',
                        httpMode: 'GET',
                        url: headNodeUrl + 'wfapi',
                        validResponseCodes: '200'
                    )
                    def nodeJson = readJSON text: responseNode.getContent()
                    def execNodeUrl = ''
                    nodeJson.stageFlowNodes.each {
                        if (it.name.toString() == 'Execution node label') {
                            // Found execution node: it.id
                            execNodeUrl = BUILD_URL + 'execution/node/' + it.id.toString() + '/log/'
                        }
                    }
                    echo execNodeUrl
                }
            }
        }
    }
}

BUILD_URL是一个全局环境变量,我假设它是由Jenkins提供的,在我的完整脚本中,我有一个stage('Stage node label') { ... },其中包含一条语句bat label: 'Execution node label', script: ...,它的日志URL将用echo构造和打印。
结果是类似http://myjenkinsserver.org:8080/job/some_folder/job/my_job_name/181/execution/node/50/log/的URL
我认为在我的例子中使用each可能并不理想,因为我不能在第一次匹配后中止它。而且我没有设法将httpRequestreadJSON封装到类方法或其他东西中,因为我不知道readJSON的返回类型。任何提示都很感谢。
希望这能帮上忙。
干杯

kx1ctssn

kx1ctssn2#

这将获取封闭节点步骤的工作区链接。您可以使用.getId()而不是.url来获取Id。这适用于我使用node {.... }的情况,但在声明性或并行步骤中使用时可能需要一些改进。

def getNodeWsUrl(flowNode = null) {
    if(!flowNode) {
        flowNode = getContext(org.jenkinsci.plugins.workflow.graph.FlowNode)
    }
    if(flowNode instanceof org.jenkinsci.plugins.workflow.cps.nodes.StepStartNode && flowNode.typeFunctionName == 'node') {
        // Could also check flowNode.typeDisplayFunction == 'Allocate node : Start'
        return "/${flowNode.url}ws/"
    }

    return flowNode.parents.findResult { getNodeWsUrl(it) }
}
o4hqfura

o4hqfura3#

我使用一些黑客。添加到sh步骤,我想得到流ID一些输出。然后在日志中找到这个字符串,并从日志中得到流ID。其中${e}是使输出唯一的循环索引。

sh "echo "Run TESTENV=TestEnv_${e}"

然后在管道的末端,为了生成URL,从日志中获取流ID。首先初始化日志索引一次。

node('master'){
   logIndex=getLogMap()

然后在循环中获取URL,其中e是循环中的索引:

stepURL=getURL(logIndex, "Run TESTENV=TestEnv_${e} ")
}

可以放入共享库的函数:

import jenkins.branch.NameMangler
import groovy.transform.Field

@Field def keyString = 'Run TESTENV=TestEnv'

def getLogMap() {
  def tokens = "${env.JOB_NAME}".tokenize('/')
  def repo = tokens[tokens.size()-2]
  try {
    def i
    def result=[:]
    exec = """
      set +x
      LOG_FILE="\$JENKINS_HOME/jobs/${repo}/branches/${NameMangler.apply(env.BRANCH_NAME)}/builds/\$BUILD_ID/log"
      LOG_INDEX_FILE="\$JENKINS_HOME/jobs/${repo}/branches/${NameMangler.apply(env.BRANCH_NAME)}/builds/\$BUILD_ID/log-index"
      LOG_LINES=\$(grep --byte-offset --text "${keyString}" "\$LOG_FILE"| sed "s/[^[:print:]\t]//g; s/\\(^[0-9]*:\\).*=\\(.*\\)/\\1 \\2/g; s/'\$//g")
      LOG_INDEX=\$(grep '.* .*' "\$LOG_INDEX_FILE")
      while read -r line ; do
          offset=\$(echo \$line | cut -d ":" -f1)
          str=\$(echo \$line | cut -d " " -f2)
          if [[ "X\$offset" == "X" ]]; then
            echo "Offset if empty in line=\$line"
            continue
          fi
          index=\$(echo "\$LOG_INDEX" | awk '\$1 > '\$offset' { print  prev; exit; } { prev = \$2 }')
          echo "\$str \$index"
      done <<< "\$LOG_LINES" | uniq
      """
    return sh(script: exec, returnStdout: true).trim().tokenize('\n')
  } catch (error) {
    throw (error)
  }
}

def getURL(logIndex, findString) {
  findString=findString.replaceAll(".*=", "")
  resultStr=logIndex.findAll { it.contains(findString) }
  def result=''
  for (s in resultStr){
    i=s.tokenize()
    result = result + "[log|${env.BUILD_URL}execution/node/" + i[1] + '/log/] '
  }
  return result.trim()
}
kfgdxczn

kfgdxczn4#

特别是,我想获得一个到我的并行分支的子流的链接,这样我就可以链接到它们的BlueOcean视图。
可以使用CpsThread.current().head.get()获取 current 线程(又称分支)的 head 流节点(最近的一步),然后可以使用FlowNode.iterateEnclosingBlocks()通过检查块开始节点是否包含ThreadNameAction的示例来查找父分支。
完整的管道示例:

import org.jenkinsci.plugins.workflow.cps.CpsThread
import org.jenkinsci.plugins.workflow.graph.FlowNode
import org.jenkinsci.plugins.workflow.actions.LabelAction
import org.jenkinsci.plugins.workflow.actions.ThreadNameAction

pipeline {
    agent any
    
    stages {
        stage("parallel start"){
            parallel {
                stage("A"){
                    steps{
                        showCurrentBranchUrls()
                        echo "Another step"
                    }
                }
                stage("B"){
                    steps{
                        echo "Some stuff"
                        showCurrentBranchUrls()
                        echo "More stuff"
                    }
                }
            }
        }
    }
}

void showCurrentBranchUrls() {
    // Get the most recent FlowNode of current branch
    FlowNode headNode = CpsThread.current().head.get()
    
    // Find the nearest parent branch
    FlowNode branchNode = getFlowNodeOfParentBranch( headNode )
    if( branchNode ) {
        // Print some useful URLs based on branchNode
        echo "Blue Ocean branch view: ${JENKINS_URL}blue/organizations/jenkins/${JOB_NAME}/detail/${JOB_BASE_NAME}/${BUILD_ID}/pipeline/${branchNode.id}"
        echo "Blue Ocean branch log: ${JENKINS_URL}blue/rest/organizations/jenkins/pipelines/${JOB_NAME}/runs/${BUILD_ID}/nodes/${branchNode.id}/log"
        echo "Pipeline steps: ${JENKINS_URL}${branchNode.url}"
    }
}

// Get FlowNode of parent branch
@NonCPS
FlowNode getFlowNodeOfParentBranch( FlowNode node ) {
    node.iterateEnclosingBlocks().find{ enclosing ->
        enclosing != null && 
        enclosing.getAction( LabelAction.class ) != null && 
        enclosing.getAction( ThreadNameAction.class ) != null
    }
}

在沙盒管道脚本中,代码可能会触发一些安全错误,所以我建议将其放入没有此类限制的shared library中。
如果你想找到 * 另一个 * 分支的节点ID而不是当前分支的节点ID,PipelineNodeGraphVisitor类就派上用场了,在SO上已经有many examples了,比如我的this one
为了进一步阅读,这里有一个关于使用Jenkins Flow Graph的很好的概述。

6fe3ivhb

6fe3ivhb5#

您可以回显或指定可以工作的节点名称,而不是获取并行作业的Step Id。

相关问题