DEV Community

Discussion on: How-To: Setup a unit-testable Jenkins shared pipeline library

Collapse
 
mccabep67 profile image
mccabep67

Thanks for the article Adrian, I have a question -
Since we are keeping the vars scripts as clean as possible do you know of any examples of how to perform more complex operations within the groovy "testable" sources?
Your example MsBuild.class is a good start but I'm struggling to find examples of an approach for performing git checkouts , builds etc. I'm new to JSL so I quite possibly have overestimated what can be wrapped in these classes.

We have tons of repeated code in Jenkins files and I have been tasked with implementing shared libraries. If I can do this create testable code that would be very cool...

Many thanks.

Collapse
 
kuperadrian profile image
Adrian Kuper

Hi, thanks for your comment :D Let me try to answer some questions:

  • If your are using declarative pipeline scripts the git checkout should be handled for you and does not have to be part of your library.
  • Jenkinsfiles are just a collection of command-line calls to be executed on the build agent. Doing a build therefore depends on the technology stack used. E.g. building a .NET Framework project would include a command-line call to MSBuild, while a Rust program would call cargo build. The latter could look somewhat like this inside the library
class CargoBuilder implements Serializable {

    void build() {
        IStepExecutor steps = ContextRegistry.getContext().getStepExecutor()

        // on windows agents use steps.bat("...")
        int returnStatus = steps.sh("cargo build")
        if (returnStatus != 0) {
            steps.error("Cargo build failed.")
        }
    }
}

Inside your Jenkinsfiles instead of writing cargo build each time, you could just write build (if your var script is called build.groovy of course). This is a very basic example (and only saves a single word), but in a real world example, the library can streamline the build process inside your organization and abstract away some of the more complicated command-line calls (e.g. by setting specific build parameters by default, which in turn can be omitted inside the Jenkinsfile).

  • I would keep the var scripts concentrated to a single task, e.g. building, testing, running metrics, deploying artifacts etc. Let's assume we wrote var scripts for all of these tasks, ex_build.groovy, ex_test.groovy, ex_metrics.groovy and ex_deploy_artifacts.groovy. Any Jenkinsfile, which before might have been very long, filled with strange parameters, paths and commands, could (more or less) be reduced to this:
@Library('my-library@1.0') _

pipeline {
    agent any
    stages {
        stage('build') {
            steps {
                ex_build
            }
        }
        stage('test') {
            steps {
                ex_test
            }
        }
        stage('metrics') {
            steps {
                ex_metrics
            }
        }
        stage('deploy artifacts') {
            steps {
                ex_deploy_artifacts 'some/path/to/artifact'
            }
        }
    }
}

(of course, this is still simplified).

Imo, the whole challenge of writing Jenkins shared libraries is collecting and wrapping all command-line calls necessary for your builds and make fitting abstractions that simplify your Jenkinsfiles and streamline your organizations build process. In the beginning this can be very difficult. (To be honest, before I was tasked in setting up Jenkins pipelines, I almost had no clue, which parts were involved in building our software. For me "a build" was just pressing the green play button in Visual Studio 🙈).

So, in your case, I would probably take a long and good look at your current Jenkinsfiles, write down which tools (e.g. MSBuild, cargo, npm, dotnet, maven, gradle) are used and how (parameters passed to the tools, which commands repeat between Jenkinsfiles, etc.). As soon as you have a good understanding of the current build process, you can start to extract the command-line calls of these tools into your library by creating fitting var scripts (e.g. ex_build.groovy) which in turn call a unit-tested "build" class. This class simply wraps the command-line call to the build tool (with steps.sh("<the command>"); or if you are using windows-based build agents steps.bat("<the command>");). That's basically it.

I hope this wall of text clears things up a bit 😅 If not, don't be shy to ask any new questions 👍

Collapse
 
mccabep67 profile image
mccabep67 • Edited

Wow.. What a fantastic reply. You do make perfect sense. I will be following your advice tomorrow. Starting by trawling through our jenkinsfiles and many many bash scripts to see what we can DRY (so to speak)
Thank you for expanding with more examples and so much detail. It's also nice to hear you started from a similar place.

Many thanks again

Regards

Patrick

Thread Thread
 
kuperadrian profile image
Adrian Kuper • Edited

You do make perfect sense.

I don't hear that very often 😄 Glad I could help