在Jenkins Pipeline中获取步骤id,以便链接到BlueOcean或Pipeline Steps视图(flo



给定一个运行一系列步骤的Jenkins管道,其中一些步骤在parallel块中,是否有任何方法可以在管道中获得给定步骤或最近步骤的Flow id?

什么是Flow ID?如果查看"管道运行"作业,可以看到指向flowGraphTable/的"管道步骤"链接。在那里,您可以链接到特定的作业步骤,如execution/node/113/。这些似乎代表了FlowNode

有没有办法从管道中获取这些ID,用于生成链接等?

特别是,我想为我的平行分支获得一个到子流的链接,这样我就可以链接到它们的BlueOcean视图。(内置的Jenkins视图是无用的,因为它没有显示子树)。

我可以看到BlueOcean链接对应于/execution/links,它们具有相同的id值。如果我的管道分支是myjob/9/execution/node/78/,那么在蓝海上它将是jobname/9/pipeline/78

但是,如果我想使用构建摘要插件或类似插件来生成链接并将其添加到构建结果页面,我该如何获得该ID?

我遇到了一个类似的用例,并设法找到了一个适合我的解决方案https://issues.jenkins-ci.org/browse/JENKINS-28119这可能是一本关于这个问题的有趣读物。它最终为我指明了一个好的方向。

除了管道和管道阶段视图插件,我还必须安装HTTP请求插件(https://wiki.jenkins.io/display/JENKINS/HTTP+Request+插件)和Pipeline Utility Steps插件(用于解析JSON,https://wiki.jenkins.io/display/JENKINS/Pipeline+实用程序+步骤+插件)。我不确定可能需要哪些其他插件。

这是我的工作示例,只缺少正在评估的阶段:

#!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提供的。在我的完整脚本中,我有一个包含语句bat label: 'Execution node label', script: ...stage('Stage node label') { ... },其日志URL将用echo构建和打印。

结果是类似http://myjenkinsserver.org:8080/job/some_folder/job/my_job_name/181/execution/node/50/log/的URL

我认为在我的示例中使用each可能并不理想,因为我不能在第一次匹配后中止它。此外,我没有设法将httpRequestreadJSON封装到类方法或其他东西中,因为我无法计算出readJSON的返回类型。如有任何提示,不胜感激。

我希望这能有所帮助。

干杯

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

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) }
}

特别是,我想获得一个到并行子流的链接分支,以便链接到它们的BlueOcean视图。

您可以使用CpsThread.current().head.get()获取当前线程(又名分支)的流节点(最新步骤)。然后,可以使用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
}
}

在沙盒管道脚本中,代码可能会触发一些安全错误,因此我建议将其放入没有此类限制的共享库中。


如果您想找到另一个分支而不是当前分支的节点ID,那么类PipelineNodeGraphVisitor就派上了用场。SO上已经有很多例子了,比如我的这个。


为了进一步阅读,这里有一个关于使用Jenkins流图的良好概述。

我使用了一些破解。添加到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()
}

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

相关内容

  • 没有找到相关文章

最新更新