Pipeline


The Jenkins job is setup as a Multibranch Pipeline. It is using:

  • Jenkins Shared Libraries to access pipeline scripts
  • Git_To_Ispw.Jenkinsfile as Jenkinsfile - stored in the source Git repository root
  • Two configuration files
  • Two Shared Library pipeline scripts - as Shared Library scripts they are both stored in the vars folder of the Shared Library repository
    • Git_MainframeCode_Pipeline.groovy - executing the mainframe code build steps
    • Git_JavaCode_Pipeline.groovy - executing the Java code build steps (as Java builds are not the scope of these tutorial pages, we do not share the code of this script)

Git_To_Ispw.Jenkinsfile

The Git_To_Ispw.Jenkinsfile specified as Jenkinsfile when setting up the Multibranch pipeline gets triggered whenever the Multibranch Pipeline detects changes in one of the branches of the source repository. In contrast to the other pipeline scripts used here, it does not take any parameters.

As mentioned before, it uses a Shared Library.

@Library('GITDEMO_Shared_Lib@Git2IspwCWC2') _

It clears the workspace from previous builds and checks out (clones) the code from the Git repository.

node {
    stage ('Checkout') {
        dir('./') {
            deleteDir()
       }
      checkout scm
   }

Important

Multibranch pipelines are directly connected to the Git repository, and the script gets executed for each branch having changes, individually. Therefore, there is no need to be more specific on the checkout.

Warning

checkout scm works only for Multibranch pipelines.

Since the following will execute the two scripts for mainframe and Java code in parallel, the content of workspace needs to be made available to the agent workspaces. This requires the use of stash in the calling script and unstash in the called script.

stash name: 'workspace', includes: '**', useDefaultExcludes: false

Important

useDefaultExcludes: false ensures that the whole workspace (including the .git folder) gets stashed. This is required by the Code Pipeline synchronization step of the Git_MainframeCode_Pipeline.

As mentioned above, the two scripts for mainframe build and Java build get executed in parallel.

    parallel(
        mfCode: {
            node {
                Git_MainframeCode_Pipeline()
           }
       },
        javaCode: {
            node {
                Git_JavaCode_Pipeline()
           }
       },
        failFast: true
    )
}

Important

failFast: true ensures that any of the two scripts gets aborted whenever a in the other script fails. This ensures immediate interruption of the job execution whenever anything fails, without having to wait for the complete build of both scripts to finish, before getting a result.

Git_MainframeCode_Pipeline.groovy

Most of the individual steps executed within this script are the same as documented in other locations and contexts already. Therefore, we document only the parts of the script that seem noteworthy.

Method call - main "flow" of the script

As Shared Library script this script implements a call method as primary method. For enhanced readability and to better follow the "flow" of the script, execution of the several stages is being implemented by separate methods within the same script.

executionFlags, a Map of Boolean variables helps to determine if a given stage shall be executed.

The script will execute:

  • stage ("Initialize") - This stage will:
    • clear the workspace
    • unstash the workspace that has been stashed by the calling script, i.e. copy the content of workspace of the main script into its own workspace
    • initialize global variables, primarily by processing the two configuration files and by determining the branch it is executing for. During initialization method determinePipelineBehavior sets the flags that determine which stages need to be executed.
  • stage('Load code to mainframe') - This stage will synchronize any changes to mainframe code into Code Pipeline.
  • If changes were detected the Code Pipeline CLI will create a file called automaticBuildParams.txt, listing information about the modified components. This file will be used by the next stage building the mainframe code. Method checkForBuildParams verifies that this file got created. If it did not created, it means, that no changes to mainframe code were responsible for the execution of the pipeline, and the build and test stages will be skipped.
  • stage('Build mainframe code')- If changes to mainframe code were detected, this stage will use the automaticBuildParams.txt to build all components that were changed. "Build" also means that any impacted components will be generated alongside.
  • stage('Execute Unit Tests') - If changes to mainframe code were detected and if "unit tests" need to be run for the current branch, this stage will execute any Total Test Virtualized tests that match components that were built by the previous stage.
  • stage('Execute Unit Tests') - If changes to mainframe code were detected and if "unit tests" need to be run for the current branch, this stage will execute any Total Test Virtualized tests that match components that were built by the previous stage.
  • stage('Execute Module Integration Tests') - If changes to mainframe code were detected and if "integration tests" need to be run for the current branch, this stage will execute any Total Test Virtualized tests that match components that were built by the previous stage.
  • If tests were executed, Code Coverage results get retrieved.
  • stage("SonarQube Scan") - This stage execute a Sonar Qube scan. This stage is executed in any case.
  • stage("Trigger Release") - If the pipeline was triggered by a merge into the main branch, this stage will trigger an XL Release template
def call(){
   stage ('Initialize') {
       dir('./') {
           deleteDir()
        }
       unstash name: 'workspace'
       initialize(execParms)
    }
   stage('Load code to mainframe') {
       if(executionFlags.mainframeChanges) {
           echo "[Info] - Loading code to mainframe level " + ispwTargetLevel + "."
           runMainframeLoad()
        }
       else {
           echo skipReason + "\n[Info] - No code will be loaded to the mainframe."
        }
    }
   checkForBuildParams(synchConfig.ispw.automaticBuildFile)
   stage('Build mainframe code') {
       if(executionFlags.mainframeChanges){
           echo "[Info] - Building code at mainframe level " + ispwTargetLevel + "."
           runMainframeBuild()
        }
       else{
           echo skipReason + "\n[Info] - Skipping Mainframe Build."
        }
    }
   if(executionFlags.executeVt){
       stage('Execute Unit Tests') {           
           runUnitTests()
        }
    }
   if(executionFlags.executeNvt){
       stage('Execute Module Integration Tests') {
           runIntegrationTests()
        }
    }
   if(executionFlags.mainframeChanges){
       getCocoResults()
    }
   stage("SonarQube Scan") {
       runSonarScan()
    }   
   if(executionFlags.executeXlr){
       stage("Trigger Release") {
           triggerXlRelease()
        }
    }
}

Configuration files

As mentioned before, this script will use two .yml files as primary source for configuration settings.

  • ispwconfig.yml contains the Code Pipeline stream and application name

  • synchconfig.yml contains all other settings, many of which correspond to the parameters that are used by the other pipelines script example shared here.

Branch mapping

Worth mentioning is the branchInfo of the synchconfig.yml file. This is used to determine the branchMapping parameter for the Git to Code Pipeline synchronization:

Each entry corresponds to a certain branch name or branch name pattern and lists

  • the target level in the Code Pipeline life cycle to use.
  • and the rule to use for creating assignments for the corresponding branch.

Method processBranchInfo is used to match the current branch to any of the entries and build the value for the branchMapping parameter. If a branch cannot be mapped, execution of the script will be aborted.

Method determinePipelineBehavior

This method, initially, determines which stages gets executed based on the current situation:

  • If this is the first build for the corresponding branch, only the Sonar scan will be executed to build a "baseline" for "new" code
  • For feature branches mainframe changes may have to by synchronized and built, and "unit tests" will be executed
  • For bugfix branches mainframe changes may have to by synchronized and built, and "unit tests" will be executed
  • For the development branch mainframe changes may have to by synchronized and built, "unit tests" and "integration tests" will be executed
  • For the main branch mainframe changes may have to by synchronized and built, "integration tests" will be executed, and an XL Release template will be triggered
def determinePipelineBehavior(branchName, buildNumber){
   if (buildNumber == "1") {
       executionFlags.mainframeChanges = false
       executionFlags.executeVt        = false
       executionFlags.executeNvt       = false
       executionFlags.executeXlr       = false
    }    
   else if (BRANCH_NAME.contains("feature")) {
       executionFlags.mainframeChanges = true
       executionFlags.executeVt        = true
       executionFlags.executeNvt       = false
       executionFlags.executeXlr       = false
    }
   else if (BRANCH_NAME.contains("bugfix")) {
       executionFlags.mainframeChanges = true
       executionFlags.executeVt        = true
       executionFlags.executeNvt       = false
       executionFlags.executeXlr       = false
    }
   else if (BRANCH_NAME.contains("development")) {
       executionFlags.mainframeChanges = true
       executionFlags.executeVt        = true
       executionFlags.executeNvt       = true
       executionFlags.executeXlr       = false
    }
   else if (BRANCH_NAME.contains("main")) {
       executionFlags.mainframeChanges = true
       executionFlags.executeVt        = false
       executionFlags.executeNvt       = true
       executionFlags.executeXlr       = true
    }
}

Method processBranchInfo

def processBranchInfo(branchInfo, ispwApplication){
   branchInfo.each {
       branchMappingString = branchMappingString + it.key + '** => ' + it.value.ispwLevel + ',' + it.value.mapRule + '\n'
       if(BRANCH_NAME.contains(it.key)) {
           ispwTargetLevel     = it.value.ispwLevel
        }
    }
}


 

Tip: For faster searching, add an asterisk to the end of your partial query. Example: cert*