It’s more than likely, you start trying to import external classes/files if you use Jenkins Job DSL Plugin. When your jobs grow to tens or hundreds of projects, some code will be common in some places. So, you will read the documentation, check the community forums, try to find out the best way to simplify your life. And you find it. You will find very quickly, that you can import classes from external files. Jenkins DSL uses Groovy, and Groovy is based on Java, after all, so it’s not a surprise. After you find that information you will try to implement that. And this is the time, in which you might face the following error: unable to resolve class utilities.MyUtilities. Of course, it will be your class and package name at the end of the error message. Oops. Not good.
Why does unable to resolve class utilities.MyUtilities appear?
There are three main, potential reasons:
- unproperly created class/package
- you are using the Job DSL Plugin in version 1.60 or above
- you are using the Job DSL Plugin in version 1.67 or above AND you are not using the Groovy sandbox
Let’s see all of these reasons in details.
Unproperly created class/package
When I write “unproperly created”, it means both created and imported. Please take a look at the documentation example (you can find that here):
So, as you can see, utilities is a reference to the directory contains a MyUtilities.groovy file. The first sentence suggests that mentioned directory should be placed at the same level as your DSL script. So the proper tree should look like this:
1 2 3 4 |
dsl_scripts/ ├── mainDSL.groovy └── utilities └── MyUtilities.groovy |
And for that matter, please be careful when you create a class. You should avoid using a public class access modifier. It’s the default modifier for any class in Groovy. I saw that in some cases if you type public before a class statement, you might encounter the unable to resolve class utilities.MyUtilities error too.
You are using the Job DSL Plugin in version 1.60 or above
That’s the second probable reason. Since Job DSL Plugin version 1.60 importing classes from the job’s workspace is not allowed anymore. According to the release notes, neither importing classes from the workspace nor the “Additional Classpath” usage is possible.
To avoid loading arbitrary code from the workspace without approval, the script directory is not added to the classpath and additional classpath entries are not supported when security is enabled. Thus importing classes from the workspace is not possible and the “Additional Classpath” option is not available.
So, as you can see, it’s not possible to import a class. But there is one way which can help you – disabling Job DSL Script Security. Of course, it’s not recommended. However, in some very special situations, it could be excused. Anyway, try to avoid this – you have one MORE way. Just upgrade your plugin to version 1.67 (at least). This version is the reason and the fix at the same time.
You are using the Job DSL Plugin in version 1.67 or above AND you are not using the Groovy sandbox
Yes, I mentioned this point to be the potential solution, but it can also the reason for your problem. Even if you upgrade your plugin to at least version 1.67, your problem may not be solved, because of some security restriction introduced to the plugin. Even though there is a possibility to add our own classpath, we cannot do that without Groovy sandbox when Script Security is enabled. It means that if you try to run your DSL script without sandbox, you also will not be able to use your classes. You can still disable Script Security, but it’s not recommended of course. If you want to see more about Script Security, please visit official GitHub repository.
The Wiki page you will be redirected to contains not only some basic information about Script Security but also a simple tutorial. You can find how to configure Jenkins to use Groovy sandboxes in general. I will try to create a more detailed tutorial, step by step, but I’m not sure when it will be possible. 🙂
+1 on making a detail tutorial
Thanks! 🙂
Is there really no way of creating the following folder structure:
dsl_scripts/
├── dsl
└── mainDSL.groovy
└── utilities
└── MyUtilities.groovy
When you have a large amount of jobs, not being able to sort them by folders quickly becomes a mess.
Of course, you can do that, but… you need to disable Script security in Security settings. If you do that, then “Additional classpath” will be available and you can type a classpath (in this scenario “utilities” directory). Unfortunately, I didn’t find any other option without the necessity of choosing only some of the possible methods inside the sandbox.
Thanks for the response, still can’t seem to get it fully working though. Might be that I’m misunderstanding something.
This is my current file structure
jobDSL/
├── builders/aws
└─── Builder.groovy
└── jobs/aws
└─── job.groovy
Builder.groovy:
package jobDSL.builders.aws
…
job.groovy:
package jobDSL.jobs.aws
import jobDSL.builders.aws.DeployStackJobBuilder
…
And I have a seed job set up to run on the following files “jobDSL/jobs/**/*.groovy”
Still I get this error when running:
ERROR: startup failed:
job.groovy: 2: unable to resolve class jobDSL.builders.aws.Builder
@ line 2, column 1.
import jobDSL.builders.aws.Builder
You almost made it correct. 😀
First at all, you should not create a package from jobs. Only classes that will be used should be packaged. In jobs, you only import necessary classes. So you’re job.groovy should look like this:
import aws.Builder
Your Builder.groovy should have:
package aws.Builder
class Builder {
...
}
Do you see the difference? In this example, I assume that jobDSL/builders is your additional classpath, that you have to add in the seed job configuration (this field will be available when script security is disabled). Packages names should contain ONLY the structure under the classpath and it would be great if they could have only one class with the same name as the package (see my code example with the class Builder).
jobDSL is just the directory of the whole project. Jobs directory contains only your jobs, and builders directory contains your helpers, builders, shared classes, or however you want to name those things. The thing is that all those paths have their own specialization and they are used according to Groovy syntax. During seed job configuration you should remember, that you need to configure it from the perspective of the root directory of the project. jobDSL as the directory should be committed (or classpath AND seed jobs paths should be extended, but it’s quite… unclear). Let’s assume that jobDSL is just the repository name with your stuff and it’s being cloned during the seed job run. So that’s your configuration:
DSL script: jobs/aws/job.groovy
Additional classpath (advanced settings): builders
Do you see how it works? With this, the job in jobs/aws/job.groovy will look for class aws.Builder, which is part of the aws.Builder package, that can be found in the builders classpath. If you want to change anything (like use jobDSL as the main directory and you want to get that directory in all the paths) just adjust this example. 😀
If anything is still unclear, don’t hesitate to ask! 🙂 And please remember about disabling the script security! Without that, you won’t even see the additional classpath field in the seed job configuration!
job.groovy is supposed to be:
package jobDSL.jobs.aws
import jobDSL.builders.aws.Builder