13

While I was able to define methods, defining variables using Jenkins Shared library seems to an unresolved quest so far.

I added /vars/True.groovy with this body

def call() {
    return true
}

And now inside Jenkinsfile, I tried to test if it works as expected:

println "evaluation ${ true == True }"

But surprise, this fails as it considers true != class True. My impression is that true == True() may work but that's not the point, I need a real variable because that whole purpose was to avoid some errors caused by people using wrong case.

So what is the magic trick?

Marco Roy
  • 4,004
  • 7
  • 34
  • 50
sorin
  • 161,544
  • 178
  • 535
  • 806
  • `vars` are supposedly instantiated as singletons, and I would think they would be bound with some sort of name. The documentation seems to imply that there should be a way to define global variables, but I'm not sure what the magic trick is. The `call()` definition I believe has it compiled as a method so I don't think that approach will work, but something like `class True implements Serializable { // some equals() definition and stuff }` I would _think_ would work. But, like you said, there is some magic Jenkins spell that needs to be cast. – mkobit Nov 09 '17 at 17:02

3 Answers3

22

I've found a way to achieve this, but with a slight caveat: the variables must be defined/wrapped within a class. However, this does have the upside of providing better organization, in order not to pollute the global space too much.

For example, we often reuse four standard "magic strings" for build statuses, which I wanted to save as global constants to facilitate interoperability. So I created a global status class, defined in vars/status.groovy:

class status {
  final String STARTED = "STARTED"
  final String SUCCESS = "SUCCESS"
  final String FAILURE = "FAILURE"
  final String ABORTED = "ABORTED"
}

The constants can then be used by referring to their parent class:

echo status.STARTED
echo status.SUCCESS
echo status.FAILURE
echo status.ABORTED

Specials thanks to @mkobit for pointing me in the right direction!

Marco Roy
  • 4,004
  • 7
  • 34
  • 50
  • 1
    This looks very interesting, first time i've seen this pattern. So, similar to how you call `vars/function.groovy` with `function()` in the pipeline, you can just use `status.STARTED` as-is as a value? Is it possible to put the same file in `/src` to not clutter up the vars folder of scripts? – Max Cascone Dec 12 '20 at 05:07
  • I no longer work with Jenkins, so I can't test it, but it should work! – Marco Roy Dec 15 '20 at 16:56
  • 1
    I tried it, it works great! It really helps clean up the pipe. – Max Cascone Dec 17 '20 at 21:04
6

It looks like Global Variables defined in the vars directory must be lower/camel/maybe some other special casing. This isn't stated anywhere in on the Defining global variables section, but there is this note at the top:

The vars directory hosts scripts that define global variables accessible from Pipeline. The basename of each *.groovy file should be a Groovy (~ Java) identifier, conventionally camelCased. The matching *.txt, if present, can contain documentation, processed through the system’s configured markup formatter (so may really be HTML, Markdown, etc., though the txt extension is required).

Here is what I tried:

  • vars/MyTrue.groovy

    class MyTrue implements Serializable {
    }
    
  • vars/myTrue.groovy

    class myTrue implements Serializable {
    }
    
  • vars/mytrue.groovy

    class mytrue implements Serializable {
    }
    
  • vars/doesCasingMatter.groovy

    class DoesCasingMatter implements Serializable {
    }
    

And in my pipeline script to test if they are instances or Class types (no script security enabled here):

echo("MyTrue: ${Class.isInstance(MyTrue)}")
echo("myTrue: ${Class.isInstance(myTrue)}")
echo("mytrue: ${Class.isInstance(mytrue)}")
echo("What bindings are there?: ${binding.variables}")

This prints out:

[Pipeline] echo
MyTrue: true
[Pipeline] echo
myTrue: false
[Pipeline] echo
mytrue: false
[Pipeline] echo
What bindings are there?: [steps:org.jenkinsci.plugins.workflow.cps.DSL@e96256, myTrue:myTrue@8a1ddc5, mytrue:mytrue@392ff649]

Which seems to indicate that something about the class name determines how it gets compiled and created. The first example, which is similar to your vars/True.groovy, is only imported as a class and not instantiated. The other two are compiled and instantiated and are bound to the script with their defined class names. I think you will have to define your classes differently if you want them to be global variables.

mkobit
  • 43,979
  • 12
  • 156
  • 150
  • This helped me come up with an answer, as I didn't know classes could be defined in the vars directory! – Marco Roy Feb 01 '19 at 00:27
1

Variable True is definitely a variable but it holds a reference to object of type True(that you defined in /vars/True.groovy). You have two options
The good one: Use it this way

println "evaluation ${ true == True() }"

The strange one: You can override equals() method in /vars/True.groovy

public boolean equals(obj) {
    return obj == true;
}

Then this should work

println "evaluation ${ true == True }"

But it would be really strange and can lead to misunderstandings.

Vitalii Vitrenko
  • 9,763
  • 4
  • 43
  • 62
  • While the first one "works" it does not address the issue of defining a variable, so is useless. The second one does not define the variable. When I try to reference it I get an exception groovy.lang.MissingPropertyException: No such property: False for class: groovy.lang.Binding Possible solutions: class at groovy.lang.Binding.getVariable(Binding.java:63) at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onGetProperty(SandboxInterceptor.java:242) – sorin Nov 09 '17 at 16:39
  • 1
    This is because `/vars/` are not imports, instead Jenkins seems to evaluate each of them and instantiate as individual classes. I am wondering if there is a way to escape this jail. The fact that the directory is called vars is really-really misleading. – sorin Nov 09 '17 at 16:40
  • @sorin Your error signals that there is no `False` variable. Are you sure you defined it in `/vars/TrueDefiner.groovy` without `def` keyword? Show your full code pls – Vitalii Vitrenko Nov 09 '17 at 16:44
  • This is not misleading because Jenkins actually create a variable with object reference. What you want to achieve is rather strange and code smell. – Vitalii Vitrenko Nov 09 '17 at 16:46