Can I have a reusable "post" block for my jenkins pipelines? - jenkins

I have many jenkins pipelines for several different platforms but my "post{}" block for all those pipelines is pretty samey. And its quite large at this point because I include success,unstable,failure and aborted in it.
Is there a way to parameterize a reusable post{} block I can import in all my pipelines? I'd like to be able to import it and pass it params as well (because while its almost the same it varies very slightly for different pipelines).
Example post block that is currently copy and pasted inside all my pipeline{}s
post {
success{
script {
// I'd like to be able to pass in values for param1 and param2
someGroovyScript {
param1 = 'blah1'
param2 = 'blah2'
}
// maybe id want a conditional here that does something with a passed in param
if (param3 == 'blah3') {
echo 'doing something'
}
}
}
unstable{
... you get the idea
}
aborted{
... you get the idea
}
failure{
... you get the idea
}
}
The following does not work:
// in mypipeline.groovy
...
post {
script {
myPost{}
}
}
// in vars/myPost.groovy
def call(body) {
def config = [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = config
body()
return always {
echo 'test'
}
}
Invalid condition "myPost" - valid conditions are [always, changed, fixed, regression, aborted, success, unstable, failure, notBuilt, cleanup]
Can i override post{} somehow or something?

Shared libraries is one approach for this, you were pretty close.
#Library('my-shared-library')_
pipeline {
...
post {
always {
script {
myPost()
}
}
}
}
Answer based on https://stackoverflow.com/a/48563538/1783362
Shared Libraries link: https://jenkins.io/doc/book/pipeline/shared-libraries/

Related

Declarative dynamic parallel stages

I figure I’m doing something unorthodox here, but I’d like to stick to declarative for convenience while dynamically generating parallel steps.
I found a way to do something like that, but mixing both paradigms, which doesn’t seem to work well with the BlueOcean UI (multiple stages inside each parallel branch do not show up properly).
The closest I got was with something like this:
def accounts() {
return ["dynamic", "list"]
}
def parallelJobs() {
jobs = []
for (account in accounts()) {
jobs[] = stage(account) {
steps {
echo "Step for $account"
}
}
}
return jobs
}
# this is inside a shared library, called by my Jenkinsfile, like what is described
# under "Defining Declarative Pipelines in Shared Libraries" in
# https://www.jenkins.io/blog/2017/09/25/declarative-1/
def call() {
pipeline {
stages {
stage('Build all variations') {
parallel parallelJobs()
}
}
}
}
The problem is Jenkins errors like this:
Expected a block for parallel # line X, column Y.
parallel parallelJobs()
^
So, I was wondering if there is a way I could transform that list of stages, returned by parallelJobs(), into the block expected by Jenkins...
Yes, you can. You need to return a map of stages. Following is a working pipeline example.
pipeline {
agent any
stages {
stage('Parallel') {
steps {
script {
parallel parallelJobs()
}
}
}
}
}
def accounts() {
return ["dynamic", "list"]
}
def parallelJobs() {
jobs = [:]
for (account in accounts()) {
jobs[account] = { stage(account) {
echo "Step for $account"
}
}
}
return jobs
}

Pass variable to JobDsl seed job (Jenkins) in scriptText?

I am working on a project and i have to configure a jenkins using JCasC (config as code plugin).
I have to create a job BUT i can't pass variables in the script.
My code:
freeStyleJob("SEED") {
parameters {
stringParam("MY_PARAMETER", "defaultValue", "A parameter")
}
steps {
jobDsl {
scriptText('''
job("seedJOB") {
displayName('${MY_PARAMETER}') // don't work
description("${MY_PARAMETER}") // don't work
//description("$MY_PARAMETER") // don't work
//description('$MY_PARAMETER') // don't work
// i tried to use triple full quotes instead of triple single quote but it's not working...
... here the job...
'''.stripIndent())
}
}
EDIT: BEST SOLUTION HERE:
i'm writing groovy code in """ quotes so if I want to evaluate variable : I don't have to put ${} just write your variable name:
With the solution:
freeStyleJob("SEED") {
parameters {
stringParam("MY_PARAMETER", "defaultValue", "A parameter")
}
steps {
jobDsl {
scriptText('''
job("seedJOB") {
displayName('MY_PARAMETER) // solution
... here the job...
'''.stripIndent())
}
}
easy!
May you could write it to a file ? You'll get something like that in your step:
steps {
shell('echo $DISPLAY_NAME > display_name.txt')
jobDsl {
scriptText('''
job("seedjob") {
String jobname = readFileFromWorkspace('display_name.txt').trim()
displayName(jobname)
}
'''.stripIndent())
}
}
You could also use a .properties file to do it more properly.

How to return a value from Jenkins function to the build stage?

I want to return the value from groovy function back to my jenkins build stage so that the value can be used as a condition in other stages. I am not able to figure out how to implement this. I have tried something like below but that didn't work.
I have Jenkinsfile something like this:
pipeline
{
agent any
stages
{
stage('Sum')
{
steps
{
output=sum()
echo output
}
}
stage('Check')
{
when
{
expression
{
output==5
}
}
steps
{
echo output
}
}
}
}
def sum()
{
def a=2
def b=3
def c=a+b
return c
}
The above approach doesn't work. Can someone provide correct implementation.
You are missing a script-step. It is necessary if you want to execute plain groovy in your Jenkinsfile. Furthermore output has to be set as global variable if you want to access it later.
def output // set as global variable
pipeline{
...
stage('Sum')
{
steps
{
script
{
output = sum()
echo "The sum is ${output}"
}
}
}
...

Is Jenkins parallel stages pipeline conditional flow, possible?

So is it possible to run the Jenkins pipeline parallel conditionally? So i have a full build that runs all the stages in parallel, however lets say I only wanted to run 2/5 of the stages... is this possible, does anyone have an idea of what this syntax would look? Here is my groovy script:
def call() {
def my_automation = load("my-lib/groovies/my_automation.groovy")
parallel thing1: {
stage('thing1'){
my_automation.my_func("thing1")
}},
thing2: {
stage('thing2'){
my_automation.my_func("thing2")
}}, thing3: {
stage('thing3'){
my_automation.my_func("thing3")
}}, thing4: {
stage('thing4'){
my_automation.my_func("thing4")
}}, thing5: {
stage('thing5'){
my_automation.my_func("thing5")
}}, thing6: {
stage('thing6'){
my_automation.my_func("thing6")
}}, thing7: {
stage('thing7') {
my_automation.my_func("thing7")
}
}
}return this;
But looking for this kind of thing:
for(all things defined, run them at once in parallel)
{
etc...
}
Is this possible?
What you need to do is conditionally define what you want to pass to parallel, rather than processing it conditionally. You would use the for loop to build a map of the things you want to execute in a map, then pass that to the parallel step:
def branches = [:]
for (int i = 0; i < 4 ; i++) {
int index = i
branches["thing${index}"] = {
stage("thing${index}") {
node ('label_example'){
my_automation.my_func("thing${index}")
}
}
}
}
parallel branches
Use logic inside there to skip adding something to the map if you want. This isn't syntax checked or tested, but you should get the idea.
I ended up doing something like this, where i would pass in the image name and match it and this would decide on which one to run against (took long enough to respond, but figured this might help someone in the future):
String call(String image_name) {
def map = ["CentOS-7":"rpm", "CentOS-6":"rpm", "RHEL-7":"rpm","RHEL-6":"rpm","Ubuntu-14.04":"deb"]
for (element in map) {
echo "${element.key} ${element.value}"
if ("${element.key}" == image_name){
return "${element.value}"}
}
return "notfound"
}
return this;

Is there a way to programmatically inject post actions in declarative pipeline

I need to share some code between several stages, which would also need to add post actions. To do so, I thought about putting everything in a method, which will be called from
pipeline {
stages {
stage('Some') {
steps {
script { commonCode() }
}
}
}
}
However, I'm not sure how could I install post actions in from commonCode. Documentation does not mention a thing. Looking at the code, implies that this DSL is basically just playing with a hash map, but I don't know would it be possible to access it from the method and modify on the fly.
Basically I would like to do something like this in commonCode:
if (something) {
attachPostAction('always', { ... })
} else {
attachPostAction('failure', { ... })
}
The only thing that works so far is that in commonCode I do:
try {
...
onSuccess()
} catch (e) {
onError()
} finally {
onAlways()
}
But was wondering if there is a more elegant way...
Now that I better understand the question (I hope)...
This is a pretty interesting idea--generate your post actions on the fly in previous stages.
It turns out to be really easy. I tried one option (success) that stored various closures in a list, then iterate through the list and run all the closures in the post action. Then I did another (failure) where I just saved a single closure as a variable and ran that. Both work well.
Below is the code that does this. Uncomment the error line to simluate a failed build.
def postSuccess = []
def postFailure
pipeline {
agent any
stages {
stage('Success'){
steps {
script {
println "Configure Success Post Steps"
postSuccess[0] = {echo "This is a successful build"}
postSuccess[1] = {
echo "Running multiple steps"
sh "ls -latr"
}
}
}
}
stage('Failure'){
steps {
script {
println "Configure Failure Post Steps"
postFailure = {
echo "This build failed"
echo "Running multiple steps for failure"
sh """
whoami
pwd
"""
}
}
// error "Simulate a failed build" //uncomment this line to make the build fail
}
}
} // stages
post {
success {
echo "SUCCESS"
script {
for (def my_closure in postSuccess) {
my_closure()
}
}
}
failure {
echo "FAILURE!"
script {
postFailure()
}
}
}
} // pipeline
You can use regular groovy scripting outside of the pipeline block. While I haven't tried it, you should be able to define a method outside of there and then call it from inside the pipeline. But method calls can't be called as steps. You would need to wrap it in a script step. But post actions take the same steps as steps{} blocks, so if you can use it insteps, you can use it in the post sections. You will need to watch scoping carefully or you will end up trying to sort out why things are null in some places.
You can also used a shared library. You could define a step in the shared library and then use it like any other step in a steps{} block or one of the post blocks.

Resources