#100DaysOfCloudDevOps Challenge — Day 11 — Groovy for Jenkins

anjanj
26 min read

Table of contents

Jenkins is a widely-used automation server, and its pipelines rely on Groovy scripting. To write efficient and maintainable Jenkins pipelines, developers need a solid understanding of Groovy fundamentals.

What is Groovy?

Groovy is a powerful, optionally typed, and dynamic language built for the Java platform. It enhances developer productivity with a concise syntax and strong Java integration. Key characteristics:

  • Groovy is built on Java and supports Java syntax.

  • It has an easy-to-learn, flexible syntax.

  • It is optionally typed and improves developer productivity.

  • Groovy code compiles to Java bytecode.

By wikipedia,

Apache Groovy is a Java-syntax-compatible object-oriented programming language for the Java platform. It is both a static and dynamic language with features similar to those of Python, Ruby, Perl, and Smalltalk. It can be used as both a programming language and a scripting language for the Java Platform, is compiled to Java virtual machine (JVM) bytecode, and interoperates seamlessly with other Java code and libraries. Groovy uses a curly-bracket syntax similar to Java's. Groovy supports closures, multiline strings, and expressions embedded in strings.

Why learn?

Our Jenkins files are writen in Groovy.

To run .groovy files in VS Code, follow these steps:

1. Install Groovy

First, you need Groovy installed on your system.

Windows

• Download and install Groovy from Groovy’s official website.

• Add Groovy to the system PATH.

Mac (Homebrew)

//find out java version
/usr/libexec/java_home -V

//export java path
export JAVA_HOME=`/usr/libexec/java_home -V`


//check java version
java -version


//install groovy
brew install groovy

Linux (Ubuntu/Debian)

sudo apt update && sudo apt install groovy

Verify installation:

groovy --version

2. Install VS Code Extensions

• Open VS Code.

• Go to Extensions (Ctrl + Shift + X).

• Search for and install “Groovy Language”.

3. Run Groovy File in VS Code

Option 1: Using the Terminal

1. Open the terminal in VS Code (Ctrl + ~).

2. Navigate to the directory where your .groovy file is located:

cd path/to/your/groovy/file

3. Run the file using:

groovy filename.groovy

Getting Started with Groovy

Java vs. Groovy

Groovy is more concise than Java:

Java:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

Groovy:

println "Hello, World!"

Key Differences:

  • Semi-colons are optional.

  • No need for class and method definitions.

  • Groovy adds convenience methods like println().

Basic Execution

println("Hello");

Output:

Hello

It simply prints “Hello” to the console.

Working with Variables

def name = "Jenkins"
int age = 10
println "Name: $name, Age: $age"
  • def is used for dynamic typing.

  • Variables can be statically typed (e.g., int).

  • Groovy supports string interpolation ("${expression}").

Dynamic Typing in Groovy

def age = "Dog";  // Initially a string
age = 40;         // Now an integer

• Groovy allows dynamic typing, meaning a variable can hold different types of values.

• age starts as "Dog" (a String) but is later reassigned to 40 (an Integer).

• No type declaration is needed (e.g., int, String).

Arithmetic Operations

println(" 5 + 4 = " + (5+4));
println(" 5 - 4 = " + (5-4));
println(" 5 * 4 = " + (5*4));
println(" 5 / 4 = " + (5.intdiv(4)));  // Integer division
println(" 5 % 4 = " + (5%4));

Output:

 5 + 4 = 9
 5 - 4 = 1
 5 * 4 = 20
 5 / 4 = 1
 5 % 4 = 1

• intdiv() is used for integer division, which discards the decimal part.

Floating-Point Operations

println(" 5.2 + 4.4 = " + (5.2.plus(4.4)));
println(" 5.2 - 4.4 = " + (5.2.minus(4.4)));
println(" 5.2 * 4.4 = " + (5.2.multiply(4.4)));
println(" 5.2 / 4.4 = " + (5.2 / 4.4));

Output:

 5.2 + 4.4 = 9.6
 5.2 - 4.4 = 0.8
 5.2 * 4.4 = 22.88
 5.2 / 4.4 = 1.1818181818181819

• Uses method calls instead of operators (plus(), minus(), multiply()).

Operator Precedence

println("3 + 2 * 5 = " + (3 + 2 * 5));  // Multiplication first
println("3 + 2 * 5 = " + ((3 + 2) * 5)); // Parentheses change order

Output:

3 + 2 * 5 = 13
3 + 2 * 5 = 25

• Standard operator precedence rules apply.

Increment and Decrement Operations

println("age++ = " + (age++)); // Post-increment: returns old value
println("++age = " + (++age)); // Pre-increment: increases first
println("age-- = " + (age--)); // Post-decrement: returns old value
println("--age = " + (--age)); // Pre-decrement: decreases first

Output:

age++ = 40
++age = 42
age-- = 42
--age = 40

• age++ returns the original value first, then increments.

• ++age increments first, then returns the new value.

Maximum and Minimum Values of Data Types

println("Biggest Int " + Integer.MAX_VALUE);
println("Smallest Int " + Integer.MIN_VALUE);

println("Biggest Float " + Float.MAX_VALUE);
println("Smallest Float " + Float.MIN_VALUE);

println("Biggest Double " + Double.MAX_VALUE);
println("Smallest Double " + Double.MIN_VALUE);

Output:

Biggest Int 2147483647
Smallest Int -2147483648
Biggest Float 3.4028235E38
Smallest Float 1.4E-45
Biggest Double 1.7976931348623157E308
Smallest Double 4.9E-324

• Integer.MAX_VALUE: Largest 32-bit integer.

• Integer.MIN_VALUE: Smallest 32-bit integer.

• Float and Double have floating-point representations.

Mathematical Operations

def randNum = 2.0;
println("Math.abs(-2.45) = " + Math.abs(-2.45));
println("Math.round(2.45) = " + Math.round(2.45));
println("Math.ceil(2.45) = " + Math.ceil(2.45));
println("Math.floor(2.45) = " + Math.floor(2.45));
println("Math.min(2, 3) = " + Math.min(3, 2));
println("Math.max(2, 3) = " + Math.max(3, 2));
println("randNum.pow(3) = " + randNum.pow(3));

Output:

Math.abs(-2.45) = 2.45
Math.round(2.45) = 2
Math.ceil(2.45) = 3.0
Math.floor(2.45) = 2.0
Math.min(2, 3) = 2
Math.max(2, 3) = 3
randNum.pow(3) = 8.0

• ceil(): Rounds up.

• floor(): Rounds down.

• randNum.pow(3): Computes .

Logarithms, Square Root, Trigonometry

println("Math.sqrt(9) = "+ Math.sqrt(9));
println("Math.cbrt(27) = "+ Math.cbrt(27));

println("Math.log(2) = " + Math.log(2));
println("Math.log10(2) = " + Math.log10(2));

println("Math.toDegrees(Math.PI) = " + Math.toDegrees(Math.PI));
println("Math.toRadians(90) = " + Math.toRadians(90));

println("Math.sin(0.5 * Math.PI) = " + Math.sin(0.5 * Math.PI));

Output:

Math.sqrt(9) = 3.0
Math.cbrt(27) = 3.0
Math.log(2) = 0.6931471805599453
Math.log10(2) = 0.3010299956639812
Math.toDegrees(Math.PI) = 180.0
Math.toRadians(90) = 1.5707963267948966
Math.sin(0.5 * Math.PI) = 1.0

• Math.sqrt(9): Square root.

• Math.cbrt(27): Cube root.

• Math.toDegrees() / Math.toRadians(): Convert radians <-> degrees.

Generating a Random Number

println("Math.abs(new Random().nextInt() % 100) + 1 = " + (Math.abs(new Random().nextInt() % 100) + 1))

Output:

Math.abs(new Random().nextInt() % 100) + 1 = <random_number>

• new Random().nextInt() % 100: Generates a random integer between -99 and 99.

• Math.abs() + 1: Ensures a positive number in the range 1 to 100.

String Interpolation

def name = "Derek";
println('I am ${name}. \n'); // Single quotes - taken literally
println("I am ${name}. \n"); // Double quotes - evaluates expression

Output:

I am ${name}. 

I am Derek.
  • Single-quoted strings do not perform interpolation, whereas double-quoted ones do.

Multi-line Strings (''' block)

def multString = '''
    I am a string 
    goes on 
    form many lines'''

println(multString)

Output:

    I am a string 
    goes on 
    form many lines
  • Triple single-quotes (''') create multi-line strings.

Indexing and Substrings

println("3rd index of name " + name[3]);       // Character at index 3
println("Index of r " + name.indexOf('r'));   // First occurrence of 'r'
println("first 3 chars of name " + name[0..2]); // Substring from index 0 to 2

Output:

3rd index of name e
Index of r 2
first 3 chars of name Der
  • Groovy allows indexing (name[3]), ranges (name[0..2]), and searching (name.indexOf('r')).

Selective Character Extraction

println("every other chars of name : " + name[0,2,4]);

Output:

every other chars of name : Drk
  • Accesses indices 0, 2, and 4 in Derek, forming Drk.

Substring Extraction

println("substring at 1 : " + name.substring(1));   // From index 1 to end
println("substring at 1 to 4 :" + name.substring(1, 4)); // From index 1 to 3

Output:

substring at 1 : erek
substring at 1 to 4 : ere
  • Works like Java’s substring(start, end).

String Concatenation

println("My name " + name);
println("My name".concat(name));

Output:

My name Derek
My nameDerek
  • concat() joins strings without an extra space.

String Repetition

println("What i said " * 2);

Output:

What i said What i said
  • Repeats the string twice.

String Comparison

println("Derek == Derek " + 'Derek'.equals('Derek'));
println("Derek == derek " + 'Derek'.equals('derek'));
println("Derek == derek " + 'Derek'.equalsIgnoreCase('derek'));

Output:

Derek == Derek true
Derek == derek false
Derek == derek true
  • equals() is case-sensitive, but equalsIgnoreCase() is not.

String Length

println("Length: " + name.length());

Output:

Length: 5
  • Finds the length.

String Manipulation

def repeatStr = "What I said is " * 2;
println(repeatStr - "What"); // Removes "What" from the first occurrence

Output:

 I said is What I said is
  • Removes only the first occurrence of "What".

String Splitting & Conversion

println(repeatStr.split(' '));  // Splits into array
println(repeatStr.toList());    // Converts to character list

Output:

[What, I, said, is, What, I, said, is]
[W, h, a, t,  , I,  , s, a, i, d,  , i, s,  , W, h, a, t,  , I,  , s, a, i, d,  , i, s,  ]
  • Splitting forms an array; toList() creates a list of characters.

String Replacement

println(repeatStr.replaceAll('I', 'she'));

Output:

What she said is What she said is
  • Replaces all occurrences of 'I' with 'she'.

Case Conversion

println("Uppercase : " + name.toUpperCase());
println("Lowercase : " + name.toLowerCase());

Output:

Uppercase : DEREK
Lowercase : derek
  • Converts case as expected.

String Comparison (<=> Operator)

println("Ant <=> Banana " + ('Ant' <=> 'Banana')); // -1 if first string comes before second
println("Banana <=> Ant " + ('Banana' <=> 'Ant')); // 1  if second string comes before first
println("Ant <=> Ant " + ('Ant' <=> 'Ant')); // 0 if they are equal

Output:

Ant <=> Banana -1
Banana <=> Ant 1
Ant <=> Ant 0
  • The spaceship operator (<=>) compares lexicographically:

  • -1 means "Ant" comes before "Banana".

  • 1 means "Banana" comes after "Ant".

  • 0 means "Ant" is equal to "Ant".

String Manipulation & Output

def randString = "Random";

println("A $randString string");
printf(" A %s string \n", randString);
printf(" %-10s %d  %.2f %10s \n", ['Stuff', 10, 1.234, 'Random']);

Explanation:

• println("A $randString string");

• Groovy supports GString interpolation, so $randString is replaced with "Random".

Output:

A Random string

• printf(" A %s string \n", randString);

• Uses C-style formatting, inserting "Random" at %s.

Output:

 A Random string

• printf(" %-10s %d %.2f %10s \n", ['Stuff', 10, 1.234, 'Random']);

• %-10s → Left-align "Stuff" in a 10-character space.

• %d → Inserts integer 10.

• %.2f → Inserts 1.234, rounded to 2 decimal places (1.23).

• %10s → Right-align "Random" in a 10-character space.

Output:

Stuff      10  1.23     Random

User Input and Arithmetic

println("Whats your name ");
def fName = System.console().readLine();
println("Hello " + fName);
  • Prompts for user input.

  • If run in Jenkins Groovy Console, interactive input (System.console().readLine()) may not work, as Jenkins often runs in a non-interactive environment.

Example interaction:

Whats your name
(User inputs: Alice)
Hello Alice
println("Enter a number ");
def num1 = System.console().readLine().toDouble();
println("Enter a number ");
def num2 = System.console().readLine().toDouble();

printf("%.2f  + %.2f = %.2f \n", num1, num2, (num1 + num2));

• Reads two numbers from the user.

• Converts them to double.

• Prints their sum formatted to 2 decimal places.

Example:

Enter a number
(User inputs: 3.5)
Enter a number
(User inputs: 2.25)
3.50  + 2.25 = 5.75

File I/O Operations

new File("test.txt").eachLine {
    line -> println "$line";
}

• Reads and prints each line from test.txt.

new File("test.txt").withWriter('utf-8'){
    writer -> writer.writeLine("Line 4");
}

• Opens test.txt in write mode, overwriting the file.

• Writes "Line 4".

File file = new File("test.txt");
file.append('Line 5');

• Appends "Line 5" to test.txt.

println(file.text);

• Prints the file contents.

Expected Output (assuming previous operations work in order):

Line 4Line 5

File Properties

println("File size :   ${file.length()} bytes");

• Prints file size in bytes.

println("File:  ${file.isFile()}");
println("Dir:  ${file.isDirectory()}");

• Checks if test.txt is a file (true).

• Checks if it is a directory (false).

File Copy & Deletion

def newFile = new File("test2.txt");
newFile << file.text;

• Copies contents of test.txt into test2.txt.

newFile.delete();

• Deletes test2.txt.

List Root Directories

def dirFiles = new File("").listRoots();
dirFiles.each {
    item -> println item.absolutePath;
}

• Lists system root directories.

Example Output (on Windows):

C:\
D:\

Example Output (on Linux/macOS):

/

Working with a List of Prime Numbers

def primes = [2, 3, 5, 7, 11, 13];

println("2nd prime " + primes[2]);  
println("2nd prime " + primes.get(2));  
println("Length: " + primes.size());

Explanation & Output:

primes[2] and primes.get(2) both fetch the element at index 2 (zero-based index).

.size() returns the length of the list.

Output:

2nd prime 5  
2nd prime 5  
Length: 6

Adding Elements to the List

primes.add(17);  // Add 17  
primes << 19;    // Append 19 using Groovy shorthand  
primes.add(23);  // Add 23

• primes list is updated with [17, 19, 23].

List Operations Without Modifying the Original List

primes + [29, 31]; // Creates a new list, does not modify primes  
primes - [31];     // Creates a new list, does not modify primes

primes + [29, 31] → Returns a new list [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31].

primes - [31] → Returns a new list, but 31 is not in primes, so it remains unchanged.

Slicing & Searching

println("1st 3 " + primes[0..2]);  
println("Matches " + primes.intersect([2, 3, 7]));

Explanation & Output:

primes[0..2] → Extracts the first three elements.

primes.intersect([2, 3, 7]) → Finds common elements.

Output:

1st 3 [2, 3, 5]  
Matches [2, 3]

Reversing & Sorting

println("Reverse " + primes.reverse());  
println("Sort " + primes.sort());

.reverse() → Returns a reversed list.

.sort() → Returns a sorted list (ascending).

Output (assuming primes were modified earlier):

Reverse [23, 19, 17, 13, 11, 7, 5, 3, 2]  
Sort [2, 3, 5, 7, 11, 13, 17, 19, 23]

Removing the Last Element

println("Last " + primes.pop());

.pop() removes and returns the last element (23).

Output:

Last 23

Nested Lists

def employee = ['List', 10, 6.25, [1, 2, 3]];  
println("2nd number " + employee[3][1]);

employee[3] accesses [1, 2, 3].

employee[3][1] retrieves the second element (2).

Output:

2nd number 2

Defining a Map (paulMap)

def paulMap = [
    'name': 'Paul',
    'age' : 35,
    'address' :" 123 Main St",
    'list': [1,2,3]
]

• A Groovy Map (similar to a dictionary in Python) is created with keys: 'name', 'age', 'address', and 'list'.

• The value for 'list' is a list (array) containing [1, 2, 3].

Accessing Map Elements

println("Name " + paulMap['name']);
println("age " + paulMap['age']);
println("list " + paulMap['list']);

Output:

Name Paul
age 35
list [1, 2, 3]

• Values are retrieved using paulMap['key'].

• The list [1,2,3] is printed in Groovy’s default format.

Adding a New Key-Value Pair to the Map

paulMap.put('city', 'Pittsburg');

• Adds a new key 'city' with the value 'Pittsburg'.

Checking for a Key and Map Size

println("Has city" + paulMap.containsKey('city'));
println("Size " + paulMap.size());

Output:

Has citytrue
Size 5

• .containsKey('city') checks if 'city' exists in the map, returning true.

• .size() returns the number of key-value pairs (now 5 after adding 'city').

Note: The output "Has citytrue" is missing a space due to string concatenation. A better way would be:

println("Has city: " + paulMap.containsKey('city'));

Iterating Over the Map

for(ele in paulMap){
    println("$ele.value : $ele.key")
}

Output:

Paul : name
35 : age
 123 Main St : address
[1, 2, 3] : list
Pittsburg : city

• The loop iterates over paulMap, treating each entry as a key-value pair (ele.key, ele.value).

• Each entry is printed in "value : key" format.

Numeric Range Example

def oneTo10 = 1..10;

println('Size: ' + oneTo10.size());  // Number of elements
println('2nd item: ' + oneTo10.get(1));  // Index starts at 0
println('Contains 11: ' + oneTo10.contains(11));  // Checks if 11 exists
println('Get Last: ' + oneTo10.getTo());  // Last element in range
println('Get First: ' + oneTo10.getFrom());  // First element in range

Expected Output:

Size: 10
2nd item: 2
Contains 11: false
Get Last: 10
Get First: 1

Alphabetical Ranges (a to z and reverse z to a)

def aToZ = 'a'..'z';
def zToA = 'z'..'a';

println('Alphabet Range Size: ' + aToZ.size());
println('First Letter: ' + aToZ.getFrom());
println('Last Letter: ' + aToZ.getTo());

println('Reverse Alphabet Range Size: ' + zToA.size());
println('First Letter in Reverse: ' + zToA.getFrom());
println('Last Letter in Reverse: ' + zToA.getTo());

Expected Output:

Alphabet Range Size: 26
First Letter: a
Last Letter: z
Reverse Alphabet Range Size: 26
First Letter in Reverse: z
Last Letter in Reverse: a

Iterating Over a Range (Useful for Jenkins Pipelines)

(1..5).each { number ->
    println("Processing number: $number")
}

Expected Output:

Processing number: 1
Processing number: 2
Processing number: 3
Processing number: 4
Processing number: 5

Using Ranges in a Jenkins Declarative Pipeline

If you’re using Groovy in a Jenkinsfile, you might do something like this:

pipeline {
    agent any
    stages {
        stage('Loop using Range') {
            steps {
                script {
                    (1..3).each { num ->
                        println("Build step $num")
                    }
                }
            }
        }
    }
}

Expected Output in Jenkins Console:

Build step 1
Build step 2
Build step 3

Comparison & Logical Operators in Groovy

Groovy supports standard comparison (==, !=, >, <, >=, <=) and logical operators (&&, ||, !) to control the flow of a program.

If-Else Conditional Example

def ageOld = 6;
if (ageOld == 5) {
    println("Got to Kindergarten");
} else if ((ageOld > 5) && (ageOld < 18)) {
    printf("Go to grade %d \n", (ageOld - 5));
} else {
    println("Go to college");
}

Explanation:

• If ageOld is exactly 5, it prints "Got to Kindergarten".

• If ageOld is between 6 and 17 (inclusive), it calculates the grade as ageOld - 5 and prints "Go to grade X".

• Otherwise, it prints "Go to college".

Output for ageOld = 6:

Go to grade 1

Since 6 > 5 and 6 < 18, it calculates 6 - 5 = 1.

Ternary Operator Example

def canVote = true;
println(canVote ? "Can Vote" : "Can't Vote");

Explanation:

• This is a ternary operator (? :), a shorthand for if-else.

• If canVote is true, it prints "Can Vote", otherwise "Can't Vote".

Output:

Can Vote

Switch Statement Example 1 (Case Matching)

switch(ageOld) {
    case 16: println("You can drive");
    case 18: println("You can vote"); break;
    default : println("Have fun");
}

Explanation:

• If ageOld is 16, it prints "You can drive", but it does not break, so it falls through.

• If ageOld is 18, it prints "You can vote" and stops because of the break statement.

• If ageOld is neither 16 nor 18, it prints "Have fun".

Output for ageOld = 6:

Have fun

Switch Statement Example 2 (Range Matching)

switch(ageOld) {
    case 0..6: println("Child"); break;    
    case 7..12: println("Teenager"); break;
    case 13..18: println("Young adult"); break;
    default : println("Adult");
}

Explanation:

• 0..6 → "Child"

• 7..12 → "Teenager"

• 13..18 → "Young adult"

• Anything else → "Adult"

Output for ageOld = 6:

Child

Because 6 falls in the range 0..6.

While Loop with Continue and Break

def i = 0;

while( i < 10){
    if( i % 2){
        i++;
        continue;
    }

    if(i == 8){
        break;
    }

    println(i);
    i++
}

Explanation:

• Starts with i = 0 and iterates while i < 10.

• if( i % 2 ) checks if i is odd. If true, i is incremented and continue skips further execution in that iteration.

• If i == 8, break exits the loop.

• Otherwise, println(i) prints i, then increments i.

Output:

0
2
4
6

(8 is not printed because of the break statement.)

For Loop with a Counter

for(i = 0; i < 5; i++){
    println(i);
}

Explanation:

• A standard for loop from 0 to 4, printing i each time.

Output:

0
1
2
3
4

For Loop Using a Range

for(j in 2..6){
    println(j);
}

Explanation:

• 2..6 represents a range from 2 to 6 (inclusive).

• j takes values from 2 to 6, printing each.

Output:

2
3
4
5
6

For Loop Iterating Over a List

def randList = [10,11,12,13,15];

for (j in randList){
    println(j);
}

Explanation:

• Iterates over the elements in randList and prints each value.

Output:

10
11
12
13
15

Greeting: sayHello() Method

static def sayHello() {
    println("Hello")
}

Explanation:

This simple method prints the greeting "Hello" to the console. When you call sayHello(), you will see the output:

Hello

Addition: getSum() Method

static def getSum(num1 = 0, num2 = 0) {
    return num1 + num2;
}

Explanation:

This method returns the sum of two numbers. Notice the default values (0) assigned to num1 and num2—if you don’t pass any arguments, the method returns 0.

Example in your main code:

Calling getSum(5, 4) returns 9, so the printed line:

println("5 + 4 = " + getSum(5,4));

displays:

5 + 4 = 9

Additional Example:

Call getSum() without arguments to see the default behavior.

println("Default sum with no arguments: " + getSum());
// Expected output: Default sum with no arguments: 0

Pass-by-Value Demonstration: passByValue() Method

static def passByValue(name) {
    name = "In function";
    println("name: " + name);
}

Explanation:

This method demonstrates that when you pass a variable to a function, reassigning it inside the function does not change the original variable.

Example in your main code:

def myName = "Chen";
passByValue(myName);
println("In main " + myName);

The output will be:

name: In function
In main Chen

Although the method prints "name: In function", the original variable myName remains "Chen".

Additional Example:

Try this with a different variable:

def sampleName = "Alice";
passByValue(sampleName); 
println("Original sampleName remains: " + sampleName);
// Expected output:
// name: In function
// Original sampleName remains: Alice

List Manipulation: doubleList() Method

static def doubleList(list) {
    def newList = list.collect { it * 2 }
    return newList;
}

Explanation:

This method takes a list of numbers and returns a new list where every element is doubled. The collect method applies the closure ({ it * 2 }) to each element in the list.

Example in your main code:

def listToDouble = [1, 2, 3, 4];
listToDouble = doubleList(listToDouble);
println(listToDouble);

This prints:

[2, 4, 6, 8]

Additional Example:

You can try the function with a different list:

def anotherList = [10, 20, 30];
println("Doubled another list: " + doubleList(anotherList));
// Expected output: Doubled another list: [20, 40, 60]

Variadic Sum: sumAll() Method

static def sumAll(int... num) {
    def sum = 0;
    num.each { sum += it }
    return sum;
}

Explanation:

This method uses varargs (indicated by int... num) to accept any number of integer arguments. It then iterates over each number, adds them up, and returns the total sum.

Example in your main code:

def nums = sumAll(1,2,3,4);
println("sum : " + nums);

The output will be:

sum : 10

Additional Example:

You can pass different sets of numbers:

println("Sum of 5, 10, 15: " + sumAll(5, 10, 15));
// Expected output: Sum of 5, 10, 15: 30

Recursion: factorial() Method

static def factorial(num) {
    if(num <= 1) {
        return 1;
    } else {
        return (num * factorial(num - 1));
    }
}

Explanation:

This method calculates the factorial of a number recursively. The factorial of a number n (written as n!) is the product of all positive integers up to n. The base case here is when num is 1 or less, in which case it returns 1.

Example in your main code:

def fact4 = factorial(4);
println("factorial of 4 : " + fact4);

This prints:

factorial of 4 : 24

Additional Example:

Test the factorial function with another value, such as 0:

println("Factorial of 0: " + factorial(0));
// Expected output: Factorial of 0: 1 (by convention, 0! is defined as 1)

Class and Method Differences: Java vs. Groovy

Java:

public class Person {
    private String name;
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

Groovy:

class Person {
    String name // No need for explicit getters/setters
}

Key Features:

  • Properties and methods use def, and types are optional.

  • No need for explicit constructors; Groovy allows named arguments.

Factorial Calculation Using Closure

def getFactorial = { num -> (num <= 1 ? 1 : num * call(num - 1)) }
println("Factorial 4: " + getFactorial(4))

Explanation:

• This closure (getFactorial) is designed to calculate the factorial of a number.

• It uses a ternary operator: If num is 1 or less, return 1; otherwise, multiply num by call(num - 1).

Expected Output

Factorial 4: 24

Closure with Variable Scope Issue Using Closure

def greeting = "Goodbye";
def sayGoodby = { theName -> println("$greeting $theName") }
sayGoodby("Chen")

Explanation:

• greeting is a string variable initialized as "Goodbye".

• The closure sayGoodby uses this variable within a string interpolation.

• When sayGoodby("Chen") is called, it prints "Goodbye Chen".

Output:

Goodbye Chen

Iterating Through a List Using Closure

def numList = [1,2,3,4]
numList.each { println(it) }

Explanation:

• numList.each { println(it) } loops through numList and prints each element.

Output:

1
2
3
4

Iterating Through a Map Using Closure

def employees = [
    'Paul' : 34,
    'Sally': 35,
    'Sam'  : 36
]
employees.each { println("$it.key : $it.value") }

Explanation:

• employees is a map where names are keys and ages are values.

• .each { println("$it.key : $it.value") } loops through the map and prints each key-value pair.

Output:

Paul : 34
Sally : 35
Sam : 36

Printing Even Numbers From a List Using Closure

def randNums = [1,2,3,4,5,6]
randNums.each { num -> if(num % 2 == 0) println(num) }

Explanation:

• Iterates through randNums.

• Prints the number if it is even.

Output:

2
4
6

Finding an Element in a List Using Closure

def nameList = ['Chen', 'Sally', 'List']
def matchEle = nameList.find { item -> item == 'Chen' }
println(matchEle)

Explanation:

• .find { item == 'Chen' } searches for "Chen" in nameList and returns it if found.

Output:

Chen

Finding All Elements Matching a Condition Using Closure

def randNumList = [1,2,3,4,5,6]
def numMatches = randNumList.findAll { item -> item > 4 }
println(numMatches)

Explanation:

• .findAll { item > 4 } returns all numbers greater than 4.

Output:

[5, 6]

Checking If Any Element Matches a Condition Using Closure

println("> 5: " + randNumList.any { item -> item > 5 })

Explanation:

• .any { item > 5 } checks if any number is greater than 5.

Output:

> 5: true

Checking If All Elements Match a Condition Using Closure

println("> 1: " + randNumList.every { item -> item > 1 })

Explanation:

• .every { item > 1 } checks if all numbers are greater than 1.

Output:

> 1: false

Filtering Even Numbers Using a Higher-Order Function Using Closure

def getEven = { num -> return (num % 2 == 0) }
def evenNums = listEdit(randNumList, getEven)
println("Evens: " + evenNums)

static def listEdit(list, clo) {
    return list.findAll(clo)
}

Explanation:

• getEven is a closure that checks if a number is even.

• listEdit is a function that applies findAll(clo) to filter elements.

• listEdit(randNumList, getEven) filters even numbers.

Output:

Evens: [2, 4, 6]

Jenkins Pipelines and Groovy

Jenkins supports two types of pipelines:

  • Declarative Pipeline: Recommended for most CI/CD workflows, structured and easy to maintain.

  • Scripted Pipeline: More flexible but harder to maintain.

Basic Declarative Pipeline

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                echo 'Building...'
                sh 'mvn clean install'
            }
        }
        stage('Test') {
            steps {
                echo 'Testing...'
                sh 'mvn test'
            }
        }
        stage('Deploy') {
            steps {
                echo 'Deploying...'
                sh 'scp target/*.jar user@server:/path/to/deploy'
            }
        }
    }
    post {
        success { echo 'Pipeline completed successfully.' }
        failure { echo 'Pipeline failed.' }
    }
}

Pipeline Structure

  • Agent: Specifies where the pipeline runs (e.g., any, none, or docker).

  • Stages: Contains multiple stage blocks.

  • Steps: Actions performed within a stage.

  • Post Block: Defines actions after success or failure.

Using Environment Variables

pipeline {
    environment {
        GITHUB_TOKEN = credentials('github-token-id')
    }
    stages {
        stage('Print Env') {
            steps {
                echo "Using GitHub Token: ${GITHUB_TOKEN}"
            }
        }
    }
}

Advanced Groovy Features in Jenkins Pipelines

Conditional Execution

stage('Deploy to Production') {
    when {
        branch 'main'
    }
    steps {
        echo 'Deploying to production...'
    }
}

Parallel Execution

stage('Parallel Tests') {
    parallel {
        stage('Unit Tests') {
            steps { sh 'mvn test -Dtest=unit' }
        }
        stage('Integration Tests') {
            steps { sh 'mvn test -Dtest=integration' }
        }
    }
}

Using Scripted Blocks

stage('Dynamic Build') {
    steps {
        script {
            if (env.BUILD_ENV == 'production') {
                echo 'Building production environment...'
            } else {
                echo 'Building development environment...'
            }
        }
    }
}

Best Practices for Writing Groovy Pipelines in Jenkins

General Best Practices

  • Use Declarative Pipelines for better readability.

  • Store Jenkinsfile in version control.

  • Use environment variables instead of hardcoded values.

  • Employ parallel stages for faster execution.

Security Considerations

  • Avoid Groovy String Interpolation with Credentials:

      environment {
          SECURE_PASSWORD = credentials('password-id')
      }
    

    Instead of:

      sh "echo ${SECURE_PASSWORD}"
    
  • Minimize Scripted Pipeline Usage: Too much Groovy in the controller can slow performance.

  • Use Shared Libraries: For reusable Groovy functions, define libraries in vars/.

Error Handling

Handling errors gracefully can save you a lot of headaches. You can use the try-catch block in Groovy to manage errors effectively:

pipeline {
    agent any 

    stages {
        stage('Build') {
            steps {
                script {
                    try {
                        sh 'make build'
                    } catch (Exception e) {
                        echo "Build failed: ${e.message}"
                        currentBuild.result = 'FAILURE'
                    }
                }
            }
        }
    }
}

Parallel Execution

If you have multiple tasks that can run simultaneously, take advantage of parallel execution. This can significantly reduce your build time. Here’s an example:

pipeline {
    agent any 

    stages {
        stage('Parallel Tasks') {
            parallel {
                stage('Task 1') {
                    steps {
                        script {
                            sh 'echo "Running Task 1"'
                        }
                    }
                }
                stage('Task 2') {
                    steps {
                        script {
                            sh 'echo "Running Task 2"'
                        }
                    }
                }
            }
        }
    }
}

Use Shared Libraries

If you find yourself repeating code across multiple pipelines, consider using shared libraries. This allows you to define common functions and variables in one place. Here’s how to set it up:

  1. Create a new repository for your shared library.

  2. Define your library structure, typically with a vars directory for global variables and a src directory for classes.

  3. In your Jenkins pipeline, load the library:

@Library('your-shared-library') _
  • Keep It Simple: Don’t overcomplicate your scripts. Simple is often better.

  • Comment Your Code: Make sure to comment on your scripts. This helps others (and your future self) understand what’s going on.

  • Test Locally: If possible, test your Groovy scripts locally before deploying them in Jenkins. This can save you time and frustration.

  • Version Control: Keep your scripts in version control. This way, you can track changes and roll back if necessary.

Automating Jenkins Tasks with Groovy

Creating a Job Programmatically

import jenkins.model.*
def jenkins = Jenkins.instance
String jobName = "MyGroovyJob"
def job = jenkins.createProject(hudson.model.FreeStyleProject, jobName)
job.description = "Created via Groovy."
job.save()
println "Job $jobName created successfully!"

Adding SCM Triggers

import hudson.triggers.*
def job = Jenkins.instance.getItem("MyGroovyJob")
job.addTrigger(new SCMTrigger("H/15 * * * *")) // Poll SCM every 15 minutes
job.save()
println "SCM trigger added successfully!"

Deleting Old Build Artifacts

import jenkins.model.*
Jenkins.instance.getAllItems(Job).each { job ->
    job.builds.findAll { it.getTimeInMillis() < System.currentTimeMillis() - (30 * 24 * 60 * 60 * 1000) }.each {
        it.delete()
        println "Deleted build #${it.number} from ${job.name}"
    }
}

Find the GitHub repo here.

If you find this blog helpful too, give back and show your support by clicking heart or like, share this article as a conversation starter and join my newsletter so that we can continue learning together and you won’t miss any future posts.

Thanks for reading until the end! If you have any questions or feedback, feel free to leave a comment.

0
Subscribe to my newsletter

Read articles from anj directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

anj
anj