Jenkins Basics
- Jenkins Build Parameters
- Invoking Downstream Job with Build Parameters
- Reading Job results from child Job
- Global Tools
- Wait until block
- Edit properties/ json file within Jenkins
- Jenkins Job kill forcibly
- How to push Code to Git repository from Jenkins
- How to run parallel stages
- Jacoco Integration in build.gradle
- Jacoco PublishReport Plugin
- Publishing Artifacts and HTMLReport to Build Job
- Jenkins HTML Display Error
- Reset Jenkins Job Number
Jenkins is the widely adopted open source continuous integration tool. You can orchestrate any application deployments using Jenkins with a wide range of plugins and native Jenkins workflows. In This Tutorial we will be demonstrates interesting features of jenkins pipeline.
In this tutorial declarative pipeline syntax will be used.
Note
|
official documentation for jenkins pipeline syntax |
Jenkins Build Parameters
Build Parameters makes jenkins job more dynamic in nature ,which prompt user before triggering the job. Following different types of build parameters that jenkins supports.
node {
env.GRADLE_USER_HOME = "${workspace}/.gradle" // pipeline specific env variable
}
pipeline { agent { label "${params.AGENT}" }
parameters {
string(name: 'AGENT', defaultValue: 'node-1 || node-2 ')
choice(name: 'BRANCH', choices: ['master', 'develop'])
booleanParam(name: 'RUN_TESTS', defaultValue: true)
password(name: 'PASSWORD', defaultValue: 'SECRET')
text(name: 'DESCRIPTION', defaultValue: 'description')
extendedChoice(name: 'OPTIONS', defaultValue: 'foo', description: '', descriptionPropertyValue: '', multiSelectDelimiter: ',', quoteValue: true, saveJSONParameterToFile: false, type: 'PT_CHECKBOX', value: 'foo,bar', visibleItemCount: 2)
}
stages {
stage('One') {
steps {
print 'Running stage one'
}
}
stage('Two') {
steps {
print 'Running stage two'
}
}
}
}
Invoking Downstream Job with Build Parameters
In Jenkins CI-CD Pipeline , a parent job can trigger child job with build parameters.
node { env.GRADLE_USER_HOME = "${workspace}/.gradle" }
pipeline {
agent any
parameters {
string(name: 'AGENT', defaultValue: 'node-1||node-2 ')
choice(name: 'BRANCH', choices: ['master', 'develop'])
booleanParam(name: 'RUN_TESTS', defaultValue: true)
password(name: 'PASSWORD', defaultValue: 'SECRET')
text(name: 'DESCRIPTION', defaultValue: 'description')
}
stages {
stage('Trigger Test Job') {
when {
expression {
// Conditional flow
return params.RUN_TESTS
}
}
steps {
script {
def jobResult = build job: "integration_tests",
propagate: true,
wait: true,
quietPeriod: 2,
parameters: [
string(name: 'BRANCH', value: params.BRANCH),
booleanParam(name: 'RUN_TESTS', value: params.RUN_TESTS),
string(name: 'AGENT', value: params.AGENT)]
}
}
}
}
}
Tip
|
propagate true means child job result sends back to parent, wait true means , parent waits until child job completes. |
Reading Job results from child Job
When Job-Parent invokes Job-Child , Job-Child can send result back to parent using enviroment variables.
pipeline {
agent any
stages {
stage('ChildJob Stage'){
steps { // run you job work
// set env variable which is available to caller (parent job)
env.setProperty("ARTIFACT_VERSION", 'GENERATED_ARTIFACT_VERSION') //(1)
}
}
}
}
-
Add env variable in the child job
pipeline {
agent any
stages {
stage('Parent Job') {
steps {
script {
def jobResult = build job: "integration_tests",
propagate: true,
wait: true,
quietPeriod: 2,
parameters: [
string(name: 'BRANCH', value: params.BRANCH),
booleanParam(name: 'RUN_TESTS', value: params.RUN_TESTS),
string(name: 'AGENT', value: params.AGENT)
]
def envVars = jobResult.getBuildVariables() // (2)
print "Received ARTIFACT_VERSION " + envVars['ARTIFACT_VERSION'] //(3)
}
}
}
}
}
-
Read env variables from child job through
jobResult.getBuildVariables()
method. -
Read key value map of env variables using envVars['ARTIFACT_VERSION'].
Global Tools
Install JDK as global tool and use it under pipeline.
pipeline {
agent any
options {
timeout(time: 80, unit: 'MINUTES')// Max time to finish this job
}
tools {
jdk 'jdk-1.8.0_212' //which will refer JDK at scratch/comdevus/jenkins/tools/hudson.model.JDK/jdk-1.8.0_212
}
}
Wait until block
node {
env.GRADLE_USER_HOME = "${workspace}/.gradle"
currentBuild.displayName = "#${env.BUILD_NUMBER}[${params.BRANCH}]" //custom build number
}
pipeline {
agent any
stages {
stage('Wait for') {
steps {
waitUntil {
script {
try {
sleep(time: 1, unit: "MINUTES")
def status = sh(returnStdout: true, script: """
docker inspect --format='{{.State.Health.Status}}' mydocker
""").trim()
(status == 'healthy')
} catch (e) {
e == 'error'
}
}
}
}
}
}
}
Edit properties/ json file within Jenkins
def updateGradleProperties() {
def fileText = readFile(file: "${env.WORKSPACE}/gradle.properties")
fileText = (fileText =~ /mykey=.*/).replaceFirst("myvalue")
writeFile(file: "${env.WORKSPACE}/gradle.properties", text: fileText)
}
def updateJson() {
def inputFile = readFile(file: "${env.WORKSPACE}/my-custom.json")
def manifestJSON = new JsonSlurper().parseText(inputFile)
def builder = new JsonBuilder(manifestJSON)
builder.content.'child' = 'value'
String result = builder.toPrettyString()
writeFile(file: "${env.WORKSPACE}/my-custom.json", text: result)
}
Jenkins Job kill forcibly
This article explains different ways to kill jenkins job forcibly.
How to push Code to Git repository from Jenkins
stage("Commit") {
steps {
dir('myrepo') {
sh("""
git checkout -b pipeline_${env.GIT_BRANCH}
git config user.name 'xxxxxx'
git config user.email 'xxxxxx@gmail.com'
git add -A
git diff-index --quiet HEAD || git commit -m 'Pipeline updated'
""")
}
}
}
stage("Push") {
environment {
GIT_AUTH = credentials('github-credential-id')
}
steps {
dir('myrepo') {
sh("""
git config --local credential.helper "!f() { echo username=\\$GIT_AUTH_USR; echo password=\\$GIT_AUTH_PSW; }; f"
git push origin HEAD:pipeline_${env.GIT_BRANCH}
""")
}
}
}
How to run parallel stages
stage('Build Parallel') {
parallel {
stage('p1'){
}
stage('p2'){
}
stage('p3'){
}
}
}
Jacoco Integration in build.gradle
Configure codeCoveragePlugin for gradle multi module project and publish report using Jenkins Job.
add below code in main build.gradle file
allprojects {
apply plugin: 'jacoco'
}
def jacocoClassDirs = fileTree(project.rootDir.absolutePath)
.include('**/build/classes/java/main/**')
//Exclude two classes with the same name for a successful build
.exclude('**/Config.class')
//Exclude swagger-generated code
.exclude('**/model/**')
.exclude('**/*Mock*.class')
task codeCoverageReport(type: JacocoReport) {
// Gather execution data from all sub-projects
// (change this if you e.g. want to calculate unit test/integration test coverage separately)
executionData fileTree(project.rootDir.absolutePath).include("**/build/jacoco/*.exec")
getClassDirectories().setFrom(jacocoClassDirs)
// Add all relevant sourcesets from the subprojects
subprojects.each {
sourceSets it.sourceSets.main
}
reports {
xml.enabled true
html.enabled true
html.destination "${buildDir}/reports/jacoco"
csv.enabled false
}
}
// always run the tests before generating the report
codeCoverageReport.dependsOn {
subprojects*.test
}
alternative way from official doc
plugins {
id 'java'
id 'jacoco'
}
repositories {
jcenter()
}
// A resolvable configuration to collect source code
def sourcesPath = configurations.create("sourcesPath") {
visible = false
canBeResolved = true
canBeConsumed = false
extendsFrom(configurations.implementation)
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'source-folders'))
}
}
// A resolvable configuration to collect JaCoCo coverage data
def coverageDataPath = configurations.create("coverageDataPath") {
visible = false
canBeResolved = true
canBeConsumed = false
extendsFrom(configurations.implementation)
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'jacoco-coverage-data'))
}
}
// Task to gather code coverage from multiple subprojects
def codeCoverageReport = tasks.register('codeCoverageReport', JacocoReport) {
additionalClassDirs(configurations.runtimeClasspath)
additionalSourceDirs(sourcesPath.incoming.artifactView { lenient(true) }.files)
executionData(coverageDataPath.incoming.artifactView { lenient(true) }.files.filter { it.exists() })
reports {
// xml is usually used to integrate code coverage with
// other tools like SonarQube, Coveralls or Codecov
xml.enabled true
// HTML reports can be used to see code coverage
// without any external tools
html.enabled true
}
}
// Make JaCoCo report generation part of the 'check' lifecycle phase
tasks.named("check") {
dependsOn(codeCoverageReport)
}
// Override coverageThreashold value
task codeCoverageVerificationRoot(type: JacocoCoverageVerification) {
executionData jacocoExecutionData
getClassDirectories().setFrom(jacocoClassDirs)
doFirst {
subprojects.findAll { subproject ->
subproject.pluginManager.hasPlugin('java')
}.each { subproject ->
additionalSourceDirs files(subproject.sourceSets.main.allJava.srcDirs)
}
}
violationRules {
rule {
limit {
minimum = new BigDecimal(codeCoverageMinimum)
}
}
}
}
Run below task to generate report
$ ./gradlew codeCoverageReport
Jacoco PublishReport Plugin
This Jacoco jenkins Publish Report plugin will helps to generate report without adding Jacoco related code as shown in Jacoco Integration in build.gradle
steps {
step([$class : 'JacocoPublisher',
execPattern : '**/build/jacoco/*.exec',
classPattern : '**/build/classes',
sourcePattern : '**/src/main/java',
exclusionPattern : '**/*Exception*,**/*Mock*,**/*Config*,**/*Log*,**/*Test*,**/*.model.*/**',
//Check this to set the build status to unstable if coverage thresholds are violated.
changeBuildStatus : true,
//Check this to set the build status to failure if the delta coverage thresholds are violated.
// Delta coverage is the difference between coverage of last successful and current build.
buildOverBuild : true,
minimumInstructionCoverage: "30",
minimumBranchCoverage : "30",
minimumComplexityCoverage : "30",
minimumLineCoverage : "30",
minimumMethodCoverage : "30",
minimumClassCoverage : "30",
skipCopyOfSrcFiles : false, //Check this to disable display of source files for each line coverage
maximumInstructionCoverage: "50",
maximumBranchCoverage : "45",
maximumComplexityCoverage : "45",
maximumLineCoverage : "65", // line coverage is base for code-coverage
maximumMethodCoverage : "70",
maximumClassCoverage : "70"
])
}
Note
|
To make build unstable you need to specify both min, max values as (min <max < Actual) |
Publishing Artifacts and HTMLReport to Build Job
If your Jenkins Job Generates TestReport / any other artifacts. you can attach it to Jenkins Job.
post {
always {
//This will attach build artifacts such as logs , jars
archiveArtifacts artifacts: 'reports/**/*', allowEmptyArchive: true, onlyIfSuccessful: false
script {
try{
// This will attach HTML report and add link in the left side panel of your Jenkins Job.
publishHTML (target: [
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'reports',
reportFiles: 'test-report.html',
reportName: 'Test Report',
escapeUnderscores: true
])
}catch(e){
print "Report Publish error "+e
}
}
}
}
Jenkins HTML Display Error
run below command in jenkins script console (https://www.thetopsites.net/article/54362637.shtml)
System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';")
Reset Jenkins Job Number
If you want to delete range of failed jobs from Jenkins pipeline , run below script from jenkins console with your job name and provide resetNumberTarget.
resetNumberTarget = 100
item = Jenkins.instance.getItemByFullName("[your_job_name]")
item.builds.each() { build ->
if(build.number >= resetNumberTarget)
{
build.delete()
}
}
item.updateNextBuildNumber(resetNumberTarget)
Comments
Post a Comment