We have a script in our Jenkins shared pipeline code which works along these lines, in src/Utils.groovy:
boolean isBuildNumberEven() {
int number = env.BUILD_NUMBER as int
(number % 2) == 0
}
This script is then used like this from one of the scripts in vars/
Utils utils = new Utils()
if (utils.isBuildNumberEven()) {
// Do something differently
}
The key point is that env apparently can be used with no problem.
Now I want to clean up the code and explicitly declare the class, so I change src/Utils.groovy like so:
class Utils {
boolean isBuildNumberEven() {
int number = env.BUILD_NUMBER as int
(number % 2) == 0
}
}
Now, I get an error about env being accessed as a global while it is not a global.
The Groovy compiler itself obviously knows how to convert a script with no class into a class, but I haven't quite figured out how to do it myself.
Is there a trick to allow me to access those global variables from the class, other than the obvious one of passing them all in via the constructor? Is there some global class somewhere which contains them all perhaps?
(Note that although I have used env for this example, it is not the only one I need to access. So yes, I already converted these to use System.getenv, but then immediately hit some other variable we were using. So there's no easy way out.)
I tried to use Tuple in a Jenkinsfile.
The line I wrote is def tupleTest = new Tuple('test', 'test2').
However, Jenkins did not accept this line and keep writing the following error to the console output:
No such constructor found: new groovy.lang.Tuple java.lang.String java.lang.String. Administrators can decide whether to approve or reject this signature.
...
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: No such constructor found: new groovy.lang.Tuple java.lang.Integer java.lang.String
...
When I visited the "Script Approval" configuration I could not see any scripts that pend approval.
Following this link, I tried to install and enable the "Permissive Security" plugin, but it did not help either - The error was the same.
I even tried to manually add the problematic signature to the scriptApproval.xml file. After I added it, I was able to see it in the list of approved signatures, but the error still remained.
Is there something I am doing wrong?
I had the same issue trying to use tuple on jenkins so I found out that I can simply use a list literal instead:
def tuple = ["test1", "test2"]
which is equivalent to
def (a, b) = ["test1", "test2"]
So now, instead of returning a tuple, I am returning a list in my method
def myMethod(...) {
...
return ["test 1", "test 2"]
}
...
def (a, b) = myMethod(...)
This is more or less a problem caused by groovy.lang.Tuple constructor + Jenkins sandbox Groovy mode. If you take a look at the constructor of this class you will see something like this:
package groovy.lang;
import java.util.AbstractList;
import java.util.List;
public class Tuple extends AbstractList {
private final Object[] contents;
private int hashCode;
public Tuple(Object[] contents) {
if (contents == null) throw new NullPointerException();
this.contents = contents;
}
//....
}
Groovy sandbox mode (enabled by default for all Jenkins pipelines) ensures that every invocation passes script approval check. It's not foolproof, and when it sees new Tuple('a','b') it thinks that the user is looking for a constructor that matches exactly two parameters of type String. And because such constructor does not exists, it throws this exception. However, there are two simple workarounds to this problem.
Use groovy.lang.Tuple2 instead
If your tuple is a pair, then use groovy.lang.Tuple2 instead. The good news about this class is that it provides a constructor that supports two generic types, so it will work in your case.
Use exact Object[] constructor
Alternatively, you can use the exact constructor, e.g
def tuple = new Tuple(["test","test2"] as Object[])
Both options require script approval before you can use them (however, in this case both constructors appear in the in-process script approval page).
I need to be able to create classes and use them within a Jenkins pipeline.
Let's say I have a very simple groovy class, declared in a groovy script, looking as this:
class MyClass {
#Override
public String toString() {
return "toto";
}
}
return MyClass();
This class is located in the folder: Project\Buildfiles\Jenkins\com\external
Then in my Jenkinsfile I would do:
node('mynode') {
toto = load 'Project\Buildfiles\Jenkins\com\external\MyClass.groovy'
echo toto.toString()
}
And this actually works
However this do pose a certain numbers of issues with my IDE which does not understand what is happening. Also, this prevents me to have several constructor in my custom class.
What I have been trying to do, and for which I need help, is the following. In a file named ExternalClasses.groovy:
class Toto{
#Override
public String toString() {
return "toto";
}
}
class Tata{
#Override
public String toString() {
return "tata";
}
}
return this;
In the JenkinsFile:
node('mynode') {
external= load 'Project\Buildfiles\Jenkins\com\external\ExternalClasses.groovy'
toto = new Toto();
tata = new Tata();
}
And this fails
I have tried several approaches, used packages names, used the Toto.new() syntax, but none worked.
Any ideas ?
Edit about Shared Libraries:
I actually have a Shared library, it is used by several teams and contains very specific data which should be own by the teams and not by the library.
We need to be able to put out of the library things which does not belong to it. The purpose of this work is to alleviate the said library of non generic code.
You could use the Shared Library Feature. Upload your scripts into a VCS like Github/Bitbucket and use Jenkins-Jobs to execute them. They are available for all projects/jobs.
I'm not a programming savvy person, so please bear with me.
I've read blog entries and docs about command object. I've never used it and was wondering if I should. (I probably should...)
My project requires parsing, sorting, calculating, and saving results into database when users upload files.
So according to one of the blog entries I read and its corresponding github code,
1) SERVICE should receive file uploads, parse uploaded files (mainly docs and pdfs), sort parsed data using RegEx, and calculate data,
2) COMMAND OBJECT should call SERVICE, collect results and send results back to controller, and save results into the database,
3) CONTROLLER should receive request from VIEW, get results from COMMAND OBJECT, and send results back to VIEW.
Did I understand correctly?
Thanks.
I found this to be the best setup. Here is an example that I use on production:
Command Object (to carry data and ensure their validity):
#grails.validation.Validateable
class SearchCommand implements Serializable {
// search query
String s
// page
Integer page
static constraints = {
s nullable: true
page nullable: true
}
}
Controller (directs a request to a Service and then gets a response back from the Service and directs this response to a view):
class SomeController {
//inject service
def someService
def search(SearchCommand cmd) {
def result = someService.search(cmd)
// can access result in .gsp as ${result} or in other forms
render(view: "someView", model: [result: result])
}
}
Service (handles business logic and grabs data from Domain(s)):
class SomeService {
def search(SearchCommand cmd) {
if(cmd.hasErrors()) {
// errors found in cmd.errors
return
}
// do some logic for example calc offset from cmd.page
def result = Stuff.searchAll(cmd.s, offset, max)
return result
}
}
Domain (all database queries are handled here):
class Stuff {
String name
static constraints = {
name nullable: false, blank: false, size: 1..30
}
static searchAll(String searchQuery, int offset, int max) {
return Stuff.executeQuery("select s.name from Stuff s where s.name = :searchQuery ", [searchQuery: searchQuery, offset: offset, max:max])
}
}
Yes, you understood it correctly except the one thing: command object shouldn't save the data to DB - let service to do that. The other advantage of command object is data binding and validation of data from the client. Read more about command objects here grails command object docs
You can also find helpful information regarding your question in this article
grails best practices
I guess not. Its not really related to whether the save is done in a service it should always attempt to carry out complex stuff and specifically db stuff in a service. so that is regardless. I tend to not use command object but have got hooked on helper classes aka beans that sit in src/main/groovy and do all of the validation and formatting. I just did a form and in it has feedback and reason.
Initially I thought I would get away with
def someAction(String feedback, String reason) {
someService.doSomething(feedback,reason)
}
But then I looked closed and my form was firstly a textarea then the selection objects were bytes so above was not working and to simply fix it without adding the complexity to my controller/service I did this:
packe some.package
import grails.validation.Validateable
class SomeBean implements Validateable {
User user
byte reason
String feedback
static constraints = {
user(nullable: true)
reason(nullable:true, inList:UsersRemoved.REASONS)
feedback(nullable:true)
}
void setReason(String t) {
reason=t as byte
}
void setFeedback(String t) {
feedback=t?.trim()
}
}
Now my controller
class SomeController {
def userService
def someService
def doSomething(SomeBean bean){
bean.user = userService.currentUser
if (!bean.validate()) {
flash.message=bean.errors.allErrors.collect{g.message([error : it])}
render view: '/someTemplate', model: [instance: bean,template:'/some/template']
return
}
someService.doSomeThing(bean)
}
}
Now my service
Class SomeService {
def doSomeThing(SomeBean bean) {
if (bean.user=='A') {
.....
}
}
All of that validation would have still had to have been done somewhere, you say no validation but in a good model you should do validation and set things to be stored in proper structures to reduce overloading your db over time. difficult to explain but in short i am talking about your domain class objects and ensuring you are not setting up String something string somethingelse and then not even defining their lenghts etc. be strict and validate
if you have a text area this will be stored in the back end - so you will need to trim it like above - you will need to ensure the input does not exceed the max character of the actual db structure which if not defined will probably be 255
and by doing
static constraints = {
user(nullable: true)
reason(min:1, max:255, nullable:true, inList:UsersRemoved.REASONS)
Has already invalidated it through the bean.validate() in the controller if the user exceeded somehow my front end checks and put in more than 255.
This stuff takes time be patient
Edited to finally add in that example byte - is one to be careful of -
When adding any String or what ever I have started to define the specific like this and in the case of byte if it is a boolean true false - fine if not then define it as a tinyint
static mapping = {
//since there is more than 1 type in this case
reason(sqlType:'tinyint(1)')
feedback(sqlType:'varchar(1000)')
// name(sqlType:'varchar(70)')
}
If you then look at your tables created in the db you should find they have been created as per definition rather than standard 255 varchar which I think is the default for a declared String.
I have the following domain class:
package com.example
class Location {
String state
def getStatesList(){
def states = ['AL','AK','AZ','AR','CA','CO','CT',
'DC','DE','FL','GA','HI','ID','IL','IN','IA',
'KS','KY','LA','ME','MD','MA','MI','MN','MS',
'MO','MT','NE','NV','NH','NJ','NM','NY','NC',
'ND','OH','OK','OR','PA','RI','SC','SD','TN',
'TX','UT','VT','VA','WA','WV','WI','WY']
return states
}
}
In my gsp, I am trying to display the state list in a select dropdown as such
<g:select name="location.state" class="form-control" from="${com.example.Location?.getStatesList()}" value="${itemInstance?.location?.state}" noSelection="['': '']" />
In doing so, I am receiving "missing method exception"
If I change the method with list, I no longer receive the error, but I don't want that.
from="${com.example.Location?.list()}" // works
from="${com.example.Location?.getStatesList()}" // does not work
Any help is greatly appreciated.
As dmahaptro said, you can correct this issue by making getStatesList() a static method.
class Location {
String state
static List<String> getStatesList() {
['AL','AK','AZ','AR','CA','CO','CT',
'DC','DE','FL','GA','HI','ID','IL','IN','IA',
'KS','KY','LA','ME','MD','MA','MI','MN','MS',
'MO','MT','NE','NV','NH','NJ','NM','NY','NC',
'ND','OH','OK','OR','PA','RI','SC','SD','TN',
'TX','UT','VT','VA','WA','WV','WI','WY']
}
}
Then you'll be able to execute Location.statesList or Location.getStatesList().
Alternative
I think a cleaner alternative is using a final constant:
class Location {
String state
static final List<String> STATES =
['AL','AK','AZ','AR','CA','CO','CT',
'DC','DE','FL','GA','HI','ID','IL','IN','IA',
'KS','KY','LA','ME','MD','MA','MI','MN','MS',
'MO','MT','NE','NV','NH','NJ','NM','NY','NC',
'ND','OH','OK','OR','PA','RI','SC','SD','TN',
'TX','UT','VT','VA','WA','WV','WI','WY']
}
Then you can access the list the same way: Location.STATES. The difference is that the all-caps name implies a value that does not change (and does not require accessing the database).
list() is a method on the domain object's metaclass. In order to do what you're trying to do you'd have to instantiate an instance of Location (or add to the metaclass). I'd personally use an Enum instead if I were you.
You have to make getStatesList() static because you are not accessing an instance of the Location class.