jenkins - Jenkins Pipeline 中的 StreamingTemplateEngine 与普通 groovy 中的 StreamingTemplateEngine
问题描述
在 Jenkins 管道中,我们想要创建一个具有可变内容的配置文件,因此我们使用 StreamingTemplateEngine。现在我们必须根据变量映射构建一个带有可选行的配置文件。这个例子是我们的第一次尝试(在开发/测试期间,首先用普通的 groovy 编写):
import groovy.text.StreamingTemplateEngine
def vars=[
"KEY2": "VAL2",
]
templateText='''
FIXKEY=FIXVAL
<%
if(KEY1) out.print "KEY1="+KEY1+"\\n";
if(KEY2) out.print "KEY2="+KEY2+"\\n";
%>
'''
def engine = new StreamingTemplateEngine()
def template=engine.createTemplate(templateText)
configContent = template.make(vars).toString()
println "CONTENT FROM TEMPLATE IS:"
println configContent;
因此,由于地图中不存在“KEY1”,我们希望生成的配置字符串为:
FIXKEY=FIXVAL
KEY2=VAL2
但是我们得到了这个例外:
Exception in thread "main" groovy.text.TemplateExecutionException: Template execution error at line 4:
3: <%
--> 4: if(KEY1) out.print "KEY1="+KEY1+"\n";
5: if(KEY2) out.print "KEY2="+KEY2+"\n";
at main.run(main.groovy:34)
at main.main(main.groovy)
Caused by: groovy.lang.MissingPropertyException: No such property: KEY1 for class: groovy.tmp.templates.StreamingTemplateScript1
所以我们了解到,模板中使用的每个变量都必须在映射中定义,因为这段代码有效:
import groovy.text.StreamingTemplateEngine
def vars=[
"KEY1": "VAL1",
"KEY2": "VAL2",
]
templateText='''
FIXKEY=FIXVAL
<%
if(KEY1) out.print "KEY1="+KEY1+"\\n";
if(KEY2) out.print "KEY2="+KEY2+"\\n";
%>
'''
def engine = new StreamingTemplateEngine()
def template=engine.createTemplate(templateText)
configContent = template.make(vars).toString()
println "CONTENT FROM TEMPLATE IS:"
println configContent;
但结果是:
FIXKEY=FIXVAL
KEY1=VAL1
KEY2=VAL2
现在我们可以在地图中定义 "KEY2":false ,但是对于大量的变量,这将是更多的工作,因为只是定义必要的变量并完全排除不需要的变量。
经过一番搜索,我们发现了这个:
StreamingTemplateEngine 异常 MissingPropertyException
我们尝试了此线程中提到的第二种解决方案:
import groovy.text.StreamingTemplateEngine
def vars=[
"KEY2": "VAL2",
].withDefault { false }
templateText='''
FIXKEY=FIXVAL
<%
if(KEY1) out.print "KEY1="+KEY1+"\\n";
if(KEY2) out.print "KEY2="+KEY2+"\\n";
%>
'''
def engine = new StreamingTemplateEngine()
def template=engine.createTemplate(templateText)
configContent = template.make(vars).toString()
println "CONTENT FROM TEMPLATE IS:"
println configContent;
并且按预期工作,生成的配置内容是:
FIXKEY=FIXVAL
KEY2=VAL2
完美,我们想,所以现在我们想在 Jenkins 流水线中使用这个片段,但是 Jenkins 在使用这个测试阶段代码时表现得有些不同:
import groovy.text.StreamingTemplateEngine
[...]
stage('test') {
def vars=[
"KEY2": "VAL2",
].withDefault { false }
templateText='''
FIXKEY=FIXVAL
<%
if(KEY1) out.print "KEY1="+KEY1+"\\n";
if(KEY2) out.print "KEY2="+KEY2+"\\n";
%>
'''
def engine = new StreamingTemplateEngine()
def template=engine.createTemplate(templateText)
configContent = template.make(vars).toString()
println "CONTENT FROM TEMPLATE IS:"
println configContent;
}
但是詹金斯的结果是这样的:
[Pipeline] {
[Pipeline] stage
[Pipeline] { (test)
[Pipeline] echo
15:07:10 CONTENT FROM TEMPLATE IS:
[Pipeline] echo
15:07:10 false
[Pipeline] }
注意单个“假”??!!
当像这样“完成”地图时:
import groovy.text.StreamingTemplateEngine
[...]
stage('test') {
def vars=[
"KEY1": "VAL1",
"KEY2": "VAL2",
].withDefault { false }
templateText='''
FIXKEY=FIXVAL
<%
if(KEY1) out.print "KEY1="+KEY1+"\\n";
if(KEY2) out.print "KEY2="+KEY2+"\\n";
%>
'''
def engine = new StreamingTemplateEngine()
def template=engine.createTemplate(templateText)
configContent = template.make(vars).toString()
println "CONTENT FROM TEMPLATE IS:"
println configContent;
}
内容字符串符合预期:
[Pipeline] stage
[Pipeline] { (test)
[Pipeline] echo
15:09:06 CONTENT FROM TEMPLATE IS:
[Pipeline] echo
15:09:06
15:09:06 FIXKEY=FIXVAL
15:09:06 KEY1=VAL1
15:09:06 KEY2=VAL2
15:09:06
15:09:06
[Pipeline] }
那么,为什么 Jenkins Pipeline Groovy 使用相同的代码片段时的行为与“普通”Groovy 不同呢?
或者甚至有完全不同的方法来解决“基于 var 的可变行 - 地图中存在”的请求?
感谢任何提示!
T0mcat
解决方案
您面临的问题的根本原因是以下表达式:
def vars=[
"KEY1": "VAL1",
"KEY2": "VAL2",
].withDefault { false }
返回一个类的实例MapWithDefault<K,V>
。此对象在 Jenkins 管道内产生问题,因为该管道使用 Groovy CPS 库进行连续传递样式转换。这种模式有一些限制。例如,它要求您在管道中使用的所有对象都必须是Serializable
.
管道脚本可以用注解标记指定的方法
@NonCPS
。然后它们会被正常编译(除了沙盒安全检查),因此其行为很像来自 Java 平台、Groovy 运行时或 Jenkins 核心或插件代码的“二进制”方法。@NonCPS
方法可以安全地使用非Serializable
对象作为局部变量,尽管它们不应接受不可序列化的参数或返回或存储不可序列化的值。您不能从@NonCPS
方法调用常规(CPS 转换)方法或流水线步骤,因此它们最好用于在将摘要传递回主脚本之前执行一些计算。特别注意@Overrides
二进制类中定义的方法,例如 Object.toString(),通常应该被标记@NonCPS
因为它通常是调用它们的二进制代码。来源:https ://github.com/jenkinsci/workflow-cps-plugin#technical-design
对于 Groovy 类,这个要求是开箱即用的,因为每个 Groovy 类都隐式地实现了Serializable
接口。在 Java 类的情况下,这个接口必须显式地实现。正如你所看到的,这个MapWithDefault<K,V>
类是一个 Java 类,它没有实现Serializable
接口。
解决方案1:将逻辑提取到@NonCPS
方法中
考虑以下示例:
import groovy.text.StreamingTemplateEngine
node {
stage('test') {
def vars=[
"KEY2": "VAL2",
]
String templateText='''
FIXKEY=FIXVAL
<%
if(KEY1) out.print "KEY1="+KEY1+"\\n";
if(KEY2) out.print "KEY2="+KEY2+"\\n";
%>
'''
configContent = parseAsConfigString(templateText, vars)
println "CONTENT FROM TEMPLATE IS:"
println configContent;
}
}
@NonCPS
def parseAsConfigString(String templateText, Map vars) {
def engine = new StreamingTemplateEngine()
def template=engine.createTemplate(templateText)
return template.make(vars.withDefault { false }).toString()
}
在这种情况下,一个方法parseAsConfigString
处理配置字符串的生成。请记住,它接受普通的哈希映射(可序列化)并将其转换到方法MapWithDefault
内部@NonCPS
,因此不可序列化对象不会在@NonCPS
方法的上下文之外使用。该StreamingTemplateEngine
对象也在方法内部使用,因为这个类没有实现Serializable
接口,所以它也会导致一些奇怪的问题。
解决方案2:ConfigObject
改用
即使带有模板引擎的解决方案可能对您有用,我还是建议您ConfigObject
改用。这个类是为表示配置对象而设计的,它有一些有用的方法。您可以从任何地图创建一个实例ConfigObject
,然后您可以调用prettyPrint()
方法来生成配置的字符串表示。考虑以下示例:
node {
stage('test') {
def map = [
KEYVAL1: "VAL2",
FIXKEY: "FIXVAL"
]
def config = new ConfigObject()
config.putAll(map)
println config.prettyPrint()
}
}
输出:
[Pipeline] stage
[Pipeline] { (test)
[Pipeline] echo
KEYVAL1='VAL2'
FIXKEY='FIXVAL'
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
这两种方法的主要区别在于,a 的漂亮打印中的字符串ConfigObject
用单引号括起来,这是我真正期望的。在这种方法中,您所要做的就是准备一个适当的地图来存储配置选项并将其转换为ConfigObject
允许您以所需的形式打印它。
推荐阅读
- sql - 在组织 InfluxDB 数据库时,这两种方法中的哪一种更受青睐?
- javascript - Fetch API (Javascript) 仅在调试时工作(断点)
- r - 计数字符串,然后是 r 中单独的字符串出现
- c# - 为字符串数组定义 XmlSchemaSet
- arrays - 数组的雪花展平查询
- react-native - Wix react-native-navigation 共享元素转换不起作用
- javascript - 隐藏/显示基于 javascript 按钮单击的视频元素
- css - 如何创建 CSS 代码以覆盖自定义 CC 中的现有主题断点?
- javascript - 打开多个选项卡,将方法附加到它们并让它们在各自的选项卡上运行
- reactjs - 用于家族树 Neo4J 和 React Tree Graph 的 JSON