Groovy list with variable name containing a dot (.) gets converted to string

老子叫甜甜 提交于 2019-12-01 08:55:16

in jenkins pipeline the env - is a variable that holds list of environment variables:

https://jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables

and environment variable could hold only a string

so when you try to assign to environment variable a list - it automatically converted to string

env.list = ["abc","def"]

equivalent to

env.list = ["abc","def"].toString()

and then you are iterating string by char...

To explain why this behavior happens, it is best to turn to the Jenkins source code.

The global variable name is env which takes us to org.jenkinsci.plugins.workflow.cps.EnvActionImpl.Binder. This binds the value to the script, which in this case is the pipeline.

Source code:

    @Override public EnvActionImpl getValue(CpsScript script) throws Exception {
        Run<?,?> run = script.$build();
        if (run != null) {
            return EnvActionImpl.forRun(run);
        } else {
            throw new IllegalStateException("no associated build");
        }
    }

EnvActionImpl extends the Groovy type GroovyObjectSupport (source code). GroovyObjectSupport has this is in its documentation:

A useful base class for Java objects wishing to be Groovy objects

So, this is Jenkins allowing a Java implementation to allow it to set it's behavior for Groovy. The methods you are using are public java.lang.Object getProperty(java.lang.String property) and public void setProperty(java.lang.String property, java.lang.Object newValue), so we will look closer at EnvActionImpl's implementation of them.

For setProperty, the implementation is here:

@Override public void setProperty(String propertyName, Object newValue) {
    env.put(propertyName, String.valueOf(newValue));
    try {
        owner.save();
    } catch (IOException x) {
        throw new RuntimeException(x);
    }
}

Looking higher up in the class we see the declaration of env is private final Map<String,String> env;. The propery name is used as the key (list in your example) and the value is the String.valueOf return of newValue, which in your case is the stringified ["abc","def"].

Looking at setProperty:

@Override public String getProperty(String propertyName) {
    try {
        CpsThread t = CpsThread.current();
        return EnvironmentExpander.getEffectiveEnvironment(getEnvironment(), t.getContextVariable(EnvVars.class), t.getContextVariable(EnvironmentExpander.class)).get(propertyName);
    } catch (Exception x) {
        LOGGER.log(Level.WARNING, null, x);
        return null;
    }
}

That can be dove into more to understand the EnvironmentExpander and CpsThread mechanics, but the quickest way is to just check the signature - public String.

This explains what Jenkins is doing under the hood with the env variable in pipeline scripts, and why your iteration is happening over a String's characters, and not the list like you might expect. If you created your own variable and tried it yourself, you would see the difference in behavior between, for example Map and the EnvActionImpl type.

final myVar = [:]
myVar.list = ["abc","def"]
env.list = ["abc","def"]
echo "${myVar.list.getClass()}"
echo "${env.list.getClass()}"
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!