In my recent work, I’ve written a lot of Groovy code. I feel like I’ve accumulated some experiences and insights worth sharing. Therefore, I decided to write this blog post, hoping to help developers who are currently or soon to be using Groovy.
I hope my readers have some experience in writing Jenkins pipelines or have used the CI/CD features provided by GitLab, GitHub, or Azure.
In this blog post, I mainly introduce some solutions for controlling the execution flow and provide some code snippets that I have used in practice.
Note: The Scripted Pipeline is used in this article.
Please refer to GitHub BlogCode
Code Debug
I highly recommend using Groovy Playground for debugging Groovy code. Although the syntax structure may not be entirely consistent, the ease of execution and result viewing will greatly increase development efficiency.”
https://onecompiler.com/groovy
Control Flow
In Groovy, the flexibility of control flow is one of its powerful features. Sequential logic is the most common, and I don’t intend to spend time on it. Instead, I will focus on concurrency and retry aspects.
For these two types of control flow, I strongly recommend using Blue Ocean for its visually appealing presentation.
Parallel
By using the ‘parallel’ keyword, I present two implementation methods.
1 | stage('run-parallel-branches') { |
Retry
The retry logic is suitable for ensuring that a task is executed correctly without rerunning the entire pipeline. Jenkins also provides a retry feature.
In the following code, an error occurs due to the generation of an even number randomly, and we can directly retry using the retry feature.
Additionally, the execution records of each retry will also be stored in the pipeline logs
1 | stage('retry') { |
Design Pattern
The following content is suitable for those who want to optimize existing complex pipeline logic. Readers who haven’t written pipelines before can skip this part.
Dependency Injection
In terms of dependency injection here, what I really mean to emphasize is inversion of control, where the control logic is abstracted away. Remember the concurrency execution methods I mentioned earlier?
If our business involves many processes, such as executing command 1 on all environments in one step, followed by executing command 2 on all environments in the next step, we might end
1 | stage('action 1') { |
Does it feel like the same logic is written twice? What if this operation is repeated many times? It will lead to a large amount of unnecessary looping in the code, which is irrelevant to the business. Here, I propose an improved solution. Its final effect remains consistent:
1 | def actionRunner = { msg, nodes, action -> |
Decorator
I used to be a proficient Python programmer, and I found that in Groovy pipelines, there are also some logics that are very suitable for decorators, such as retry logic.
We’ve discussed its implementation in the control flow above, but we actually have a more elegant solution. When you want to add similar functionality to many functions, decorators are a great choice.
The following code can work well in the Groovy Playground
but cannot be used in Jenkins pipelines.
Here, I provide two approaches for writing decorators. You can choose one:
curry wrapper
1 | // curry wrapper |
simple wrapper
This approach is easier to understand, after all, not everyone would consider using function currying.
1 | def testWrapper(Closure job) { |
It’s unfortunate that neither of these approaches can be directly used in Jenkins. They will result in the following error:
The core reason is that there is an issue with variable parameters in Jenkins Closure. Here, when we wrote Object… args for variable parameters, it only reads the first parameter as read-only.
Jenkins wrapper
Here’s an alternative solution I propose: using a function wrapped in a wrapper, passing parameters as an array. We’ll fill in the parameters separately when making the actual call.
1 | def retryWrapper(Closure job) { |
Conclusion
This blog post mainly aims to summarize the issues I’ve recently encountered and the design patterns I’ve prepared to address them. I hope it can be helpful for friends who use Jenkins. If there are better solutions from experts, please don’t hesitate to share your insights.