Imports of plugins classes not resolving when in vars/foo.groovy shared library

Greetings. I’ve minimized the following snippet of Scripted Pipeline groovy code:

import org.jenkinsci.plugins.docker.workflow.Docker as Docker

Docker.Image buildBuilderImage() {
    sh "mkdir -vp empty"
    writeFile file:'empty/Dockerfile', text:'''FROM scratch'''
    def img = docker.build "jenkins-local-image:test", "empty/"
    return img
}
void test() {
    Docker.Image img = buildBuilderImage()
    println "img is of type ${img.getClass()}"
}

pipeline {
    agent any
    stages {
        stage('Test') {
            steps {
                println GroovySystem.version
                test()
            }
        }
    }
}

When put into a dummy Pipeline Script sandbox job, and after approvals of script signatures, it works as expected :white_check_mark::

[...]
09:48:08  #3 writing image sha256:471a1b8817eefb6569017c1a76f288e0d4e5c8476eb199485c469d0b033168bf done
09:48:08  #3 naming to docker.io/library/jenkins-local-image:test done
09:48:08  #3 DONE 0.0s
09:48:08  [Pipeline] echo
09:48:08  img is of type class org.jenkinsci.plugins.docker.workflow.Docker$Image

Now, I need to move these two functions, test() and buildBuilderImage() into shared library (because in reality, they’re more complex than the FROM scratch minimal sample).

So I do have a global library already set up, and I’m moving this code to a new file vars/temporaryScript.groovy:

# vars/temporaryScript.groovy

import org.jenkinsci.plugins.docker.workflow.Docker

Docker.Image buildBuilderImage() {
    sh "mkdir -vp empty"
    writeFile file:'empty/Dockerfile', text:'''FROM scratch'''
    def img = docker.build "jenkins-local-image:test", "empty/"
    return img
}

def test() {
    Docker.Image img = buildBuilderImage()
    println "img is of type ${img.getClass()}"
}

def call() {
    test()
}

And then, the next test also works :white_check_mark:

pipeline {
    agent any
    stages {
        stage('Test') {
            steps {
                println GroovySystem.version
                temporaryScript.test()
            }
        }
    }
}

However, what does not work :cross_mark: is if I create a git repository with a Jenkinsfile like so:

# Jenkinsfile
node {
    temporaryScript.test()
}

Then a job automatically appears, in an org-wide job folder, /acme-org/job/jenkinsfile-sandbox/job/main — which if triggered, fails like so:

10:08:19  org.jenkinsci.plugins.workflow.cps.CpsCompilationErrorsException: startup failed:
10:08:19  /var/lib/jenkins/jobs/acme-org/jobs/jenkinsfile-sandbox/branches/main/builds/1/libs/3f141a404141094f72f570245bea9bd286af78ea3be4efe6ff08a2ce65e943d4/vars/temporaryScript.groovy: 3: unable to resolve class Docker.Image 
10:08:19   @ line 3, column 1.
10:08:19     Docker.Image buildBuilderImage() {
10:08:19     ^
10:08:19  
10:08:19  1 error
10:08:19  
10:08:19  	at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:309)
10:08:19  	at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:981)
10:08:19  	at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:626)
10:08:19  	at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:575)
10:08:19  	at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:323)
10:08:19  	at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:293)
  [...]

The error disappears if I add import org.jenkinsci.plugins.docker.workflow.Docker into the Jenkinsfile — but unfortunately, editing Jenkinsfiles in multiple branches over hundreds of repos is not something I can do easily here. It also doesn’t look right.

Anything I’m missing? How to fix this? (without import in the Jenkinsfile)

I am aware about java classpath — and had seen advice to check it with unable to resolve class errors. But I’m completely lost regarding where exactly am I supposed to check those classpaths; Jenkins User Documentation is super scarce around this.

Setup details:

  • Jenkins 2.387.1 (this is old; I know)
  • Among all the nearly 200 plugins, I’ve got these
docker-workflow:1.26
multiple-scms:0.8
github:1.34.5
github-api:1.303-400.v35c2d8258028
github-branch-source:1677.v731f745ea_0cf

So I do have a global library already set up, and I’m moving this code to a new file vars/temporaryScript.groovy […] And then, the next test also works

Silly question, but did it actually work for you?

I tried using your temporaryScript.groovy in our sharedlib, and it promptly fails both on a simplest Declarative and Scripted pipeline on our Jenkins 2.502, with the same errors.

Looking at the implementation of Docker.Image, I see that it is a static class inside Docker:

public class Docker {
  public static class Image {

I made a stub with such static class in our sharedlib, and tried to use it in the same Scripted pipeline:

// src/com/pany/sharedlib/ClassWithStatic.groovy
package com.pany.sharedlib
class ClassWithStatic {
    public static class Inside {
    }
}

// vars/declare.groovy
import com.pany.sharedlib.ClassWithStatic
ClassWithStatic.Inside buildBuilderImage() {
    return null
}
void test() {
    ClassWithStatic.Inside img = buildBuilderImage()
    println("${img}")
}
return this

// pipeline
@Library('JenkinsSharedLib@declarative-import') _
node('docker') {
    stage('Test') {
        declare.test()
    }
}

Same errors once again:

21:09:48  /.../vars/declare.groovy: 3: unable to resolve class ClassWithStatic.Inside 
21:09:48   @ line 3, column 1.
21:09:48     ClassWithStatic.Inside buildBuilderImage() {
21:09:48  /.../vars/declare.groovy: 7: unable to resolve class ClassWithStatic.Inside 
21:09:48   @ line 7, column 28.
21:09:48         ClassWithStatic.Inside img = buildBuilderImage()

And just like you said – if I add import com.pany.sharedlib.ClassWithStatic right after the @Library _, the pipeline executes just fine.

I believe this might be some manifestation of the dreaded “Jenkins Pipelines Are Not Really Groovy” curse. We might want to report it to Jenkins Jira :thinking:

Spoiler with full stack trace, for history sake
21:09:48  /var/jenkins_home/jobs/sandbox/jobs/tmp-random/builds/45/libs/e084e86b41320f3225297482546c87d83193fe784adaca1f1f3e446c5a64d85c/vars/declare.groovy: 3: unable to resolve class ClassWithStatic.Inside 
21:09:48   @ line 3, column 1.
21:09:48     ClassWithStatic.Inside buildBuilderImage() {
21:09:48     ^
21:09:48  
21:09:48  /var/jenkins_home/jobs/sandbox/jobs/tmp-random/builds/45/libs/e084e86b41320f3225297482546c87d83193fe784adaca1f1f3e446c5a64d85c/vars/declare.groovy: 7: unable to resolve class ClassWithStatic.Inside 
21:09:48   @ line 7, column 28.
21:09:48         ClassWithStatic.Inside img = buildBuilderImage()
21:09:48                                ^
21:09:48  
21:09:48  2 errors
21:09:48  
21:09:48  	at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:309)
21:09:48  	at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:981)
21:09:48  	at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:626)
21:09:48  	at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:575)
21:09:48  	at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:323)
21:09:48  	at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:293)
21:09:48  	at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:279)
21:09:48  	at groovy.lang.GroovyClassLoader.recompile(GroovyClassLoader.java:786)
21:09:48  	at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:743)
21:09:48  	at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:812)
21:09:48  	at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
21:09:48  	at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsGroovyShell$TimingLoader.loadClass(CpsGroovyShell.java:216)
21:09:48  	at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
21:09:48  	at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:702)
21:09:48  	at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:812)
21:09:48  	at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:800)
21:09:48  	at PluginClassLoader for pipeline-groovy-lib//org.jenkinsci.plugins.workflow.cps.global.UserDefinedGlobalVariable.getValue(UserDefinedGlobalVariable.java:52)
21:09:48  	at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsScript.getProperty(CpsScript.java:137)
21:09:48  	at org.codehaus.groovy.runtime.InvokerHelper.getProperty(InvokerHelper.java:190)
21:09:48  	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.getProperty(ScriptBytecodeAdapter.java:469)
21:09:48  	at PluginClassLoader for script-security//org.kohsuke.groovy.sandbox.impl.Checker$7.call(Checker.java:377)
21:09:48  	at PluginClassLoader for script-security//org.kohsuke.groovy.sandbox.GroovyInterceptor.onGetProperty(GroovyInterceptor.java:68)
21:09:48  	at PluginClassLoader for script-security//org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onGetProperty(SandboxInterceptor.java:347)
21:09:48  	at PluginClassLoader for script-security//org.kohsuke.groovy.sandbox.impl.Checker$7.call(Checker.java:375)
21:09:48  	at PluginClassLoader for script-security//org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(Checker.java:379)
21:09:48  	at PluginClassLoader for script-security//org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(Checker.java:355)
21:09:48  	at PluginClassLoader for script-security//org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(Checker.java:355)
21:09:48  	at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.sandbox.SandboxInvoker.getProperty(SandboxInvoker.java:29)
21:09:48  	at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.LoggingInvoker.getProperty(LoggingInvoker.java:134)
21:09:48  	at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.impl.PropertyAccessBlock.rawGet(PropertyAccessBlock.java:20)
21:09:48  	at WorkflowScript.run(WorkflowScript:5)
21:09:48  	at ___cps.transform___(Native Method)
21:09:48  	at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.impl.PropertyishBlock$ContinuationImpl.get(PropertyishBlock.java:73)
21:09:48  	at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.LValueBlock$GetAdapter.receive(LValueBlock.java:30)
21:09:48  	at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.impl.PropertyishBlock$ContinuationImpl.fixName(PropertyishBlock.java:65)
21:09:48  	at jdk.internal.reflect.GeneratedMethodAccessor305.invoke(Unknown Source)
21:09:48  	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
21:09:48  	at java.base/java.lang.reflect.Method.invoke(Unknown Source)
21:09:48  	at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
21:09:48  	at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
21:09:48  	at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.Next.step(Next.java:83)
21:09:48  	at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:147)
21:09:48  	at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$001(SandboxContinuable.java:17)
21:09:48  	at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:49)
21:09:48  	at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:180)
21:09:48  	at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:419)
21:09:48  	at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:327)
21:09:48  	at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:292)
21:09:48  	at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService.lambda$wrap$4(CpsVmExecutorService.java:140)
21:09:48  	at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
21:09:48  	at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:139)
21:09:48  	at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
21:09:48  	at jenkins.security.ImpersonatingExecutorService$1.run(ImpersonatingExecutorService.java:68)
21:09:48  	at jenkins.util.ErrorLoggingExecutorService.lambda$wrap$0(ErrorLoggingExecutorService.java:51)
21:09:48  	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
21:09:48  	at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
21:09:48  	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
21:09:48  	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
21:09:48  	at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$1.call(CpsVmExecutorService.java:53)
21:09:48  	at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$1.call(CpsVmExecutorService.java:50)
21:09:48  	at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:136)
21:09:48  	at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:275)
21:09:48  	at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService.lambda$categoryThreadFactory$0(CpsVmExecutorService.java:50)
21:09:48  	at java.base/java.lang.Thread.run(Unknown Source)
21:09:49  Finished: FAILURE
1 Like

Hi @Artalus, thanks for checking :slight_smile:

Yep, it did; I imagine there’s been a lot of changes in jenkins since 2.387.1 up to 2.502.

I’m trying to minimize this further & replicate on a clean-slate docker image… But it’s getting fiddly to find compatible versions of all the plugins.

Yep, I figured as much :thinking: Would rather find a workaround instead of hitting the Jira… I imagine, there’d be over 9000 similar or related reports already open there.

I tried to reproduce the problems on a current Jenkins (2.506). Here I get it working by not using strict typing but just using def

The library is configured globally and to load implicitly so I don’t need to import it in the Jenkinsfile

So here is the working version for me:

# vars/temporaryScript.groovy

def buildBuilderImage() {
    sh "mkdir -vp empty"
    writeFile file:'empty/Dockerfile', text:'''FROM scratch'''
    def img = docker.build "jenkins-local-image:test", "empty/"
    return img
}

def test() {
    def img = buildBuilderImage()
    println "img is of type ${img.getClass()}"
}

And the Jenkinsfile

pipeline {
    agent {
        label 'linuxx86_64'
    }
    stages {
        stage('Test') {
            steps {
              script {
                println GroovySystem.version
                temporaryScript.test()
              }
            }
        }
    }
}
1 Like