Reputation: 131
I'm able to use @Grab annotation in Jenkins pipeline script in both, sandbox and non-sandbox mode. My problem is that dependencies are by default resolved against maven central repository but I need to get them resolved against our company Artifactory. To demonstrate the issue here's simple pipeline script:
//@Grab(group = 'my.compay', module='my-module-name', version='1.0.0-SNAPSHOT')
//import my.company.MyFancyClass
@Grab('com.google.guava:guava:23.0')
import com.google.common.base.Joiner
pipeline {
agent any
stages {
stage('Grape Test') {
steps {
echo "Joiner: ${Joiner.class}"
// echo "MyFancyClass: ${MyFancyClass.class}"
}
}
}
}
When I run script as it is, I'll see in output fully qualified name of the class. However, when I uncomment commented lines which works with dependency exposed in company artifactory script fails with following:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
WorkflowScript: 2: unable to resolve class my.company.MyFancyClass
@ line 1, column 1.
@Grab(group = 'my.company', module='my-module-name', version='1.0.0-SNAPSHOT')
1 error
After investigation I find out that @Grab uses Groovy specific thing called Grape which is described here. Mentioned documentation describes how to configure Grape to use your own repositories using ivy settings in file ~/.groovy/grapeConfig.xml. I found solution and make it working for regular Groovy code with following content:
<ivysettings>
<settings defaultResolver="downloadGrapes"/>
<credentials host="localhost" realm="Artifactory Realm" username="USERNAME" passwd="PASSWORD"/>
<resolvers>
<chain name="downloadGrapes" returnFirst="true">
<filesystem name="cachedGrapes">
<ivy pattern="${user.home}/.groovy/grapes/[organisation]/[module]/ivy-[revision].xml"/>
<artifact pattern="${user.home}/.groovy/grapes/[organisation]/[module]/[type]s/[artifact]-[revision](-[classifier]).[ext]"/>
</filesystem>
<ibiblio name="localm2" root="file:${user.home}/.m2/repository/" checkmodified="true" changingPattern=".*" changingMatcher="regexp" m2compatible="true"/>
<ibiblio name="maven-release" m2compatible="true" root="http://artifactory:8081/artifactory/maven-release"/>
<ibiblio name="maven-snapshot" m2compatible="true" root="http://artifactory:8081/artifactory/maven-snapshot"/>
<!-- todo add 'endorsed groovy extensions' resolver here -->
<ibiblio name="jcenter" root="https://jcenter.bintray.com/" m2compatible="true"/>
<ibiblio name="ibiblio" m2compatible="true"/>
</chain>
</resolvers>
</ivysettings>
Unfortunately this ivy configuration file fixing my problem only partialy, and make Grapes working as expected only from regular Groovy code (same for calling grape command from CLI).
I have no idea how Jenkins uses Groovy/Grapes but it obviously ignores default location for this file. I read some Ivy docs and found out that can customize some ivy settings like for example config file location using properties. That's why I tried to convice my Jenkins to effectively use my config by running it from CLI using following command:
java -Divy.settings.file=%USERPROFILE%\.groovy\grapeConfig.xml -jar jenkins.war --httpPort=8888
That really works but to my surprise again only partially! It works only when I switch off sandbox mode for my pipeline script. If I switch it on, I'm again able to @Grab dependencies only from maven central.
Any ideas are very welcome...
Upvotes: 7
Views: 2341
Reputation: 131
Since I haven't received any answers I went a little bit further with investigation and got to interesting conclusion. First of all let's take a look on modified pipeline script:
@Grab('com.google.guava:guava:23.0')
import com.google.common.base.Joiner
pipeline {
agent any
stages {
stage('Grape Test') {
steps {
echo "Joiner: ${Joiner.class}"
script {
Joiner.class.getClassLoader().getURLs().each { url ->
println url.toExternalForm()
}
}
}
}
}
}
I ran this script in sandbox mode where dependency to Guava seems to be satisfied from maven central. Surprisingly I found out that Joiner was taken from else where as shown in shortened output:
...
[Pipeline] echo
Joiner: class com.google.common.base.Joiner
...
[Pipeline] echo
file:/C:/Users/mrohac/.jenkins/war/WEB-INF/lib/guava-11.0.1.jar
...
So, this test proved that @Grab is in sandbox mode silently ignored while import works and loads classes from classpath. This led me to the conclusion that the only way how to use in pipeline script proprietary code written in Java is to put related dependencies on classpath.
So, the conclusion for enabling use of custom Java code in pipeline script is to put related dependencies on classpath. For me as a Jenkins newbie is questionable what should be the best way to achieve this. Anyhow, I can confirm that putting dependencies to ~/.jenkins/war/WEB-INF/lib/
effectively enables use of desired classes in pipeline scripts.
Of course this may imply new sort of issues, especially related to overall Jenkins stability which can be harmed by clashes on classpath. Anyhow, I hope that pipeline script could be extended with custom Java code with minimized impact on Jenkins stability, for example by introducing dedicated pipeline plugin for registering and loading such dependencies.
Upvotes: 6