Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Example for a full blown Jenkins pipeline script with multiple stages, kubernetes templates, shared volumes, input steps, injected credentials, heroku deploy, sonarqube and artifactory integration, Docker containers, multiple Git commit statuses, PR merge vs branch build detection, REST API calls to GitHub deployment API, stage timeouts, stage c…
#!groovy
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
def label = "mypod-${UUID.randomUUID().toString()}"
podTemplate(label: label, yaml: """
spec:
containers:
- name: mvn
image: maven:3.3.9-jdk-8-alpine
command:
- cat
tty: true
volumeMounts:
- mountPath: /cache
name: maven-cache
volumes:
- name: maven-cache
hostPath:
# directory location on host
path: /tmp
type: Directory
""",
{
node (label) {
container ('mvn') {
// env.JAVA_HOME="${tool 'Java SE DK 8u131'}"
// env.PATH="${env.JAVA_HOME}/bin:${env.PATH}"
server = Artifactory.server "artifactory"
buildInfo = Artifactory.newBuildInfo()
buildInfo.env.capture = true
// pull request or feature branch
if (env.BRANCH_NAME != 'master') {
checkout()
build()
unitTest()
// test whether this is a regular branch build or a merged PR build
if (!isPRMergeBuild()) {
// maybe disabled for ChatOps
preview()
} else {
// PR-build
sonar()
}
} // master branch / production
else {
checkout()
build()
allTests()
preProduction()
manualPromotion()
production()
}
}
}
})
def isPRMergeBuild() {
return (env.BRANCH_NAME ==~ /^PR-\d+$/)
}
def checkout () {
stage 'Checkout code'
context="continuous-integration/jenkins/"
context += isPRMergeBuild()?"pr-merge/checkout":"branch/checkout"
checkout scm
setBuildStatus ("${context}", 'Checking out completed', 'SUCCESS')
}
def build () {
stage 'Build'
mvn 'clean install -DskipTests=true -Dmaven.javadoc.skip=true -Dcheckstyle.skip=true -B -V'
}
def unitTest() {
stage 'Unit tests'
mvn 'test -B -Dmaven.javadoc.skip=true -Dcheckstyle.skip=true'
if (currentBuild.result == "UNSTABLE") {
sh "exit 1"
}
}
def sonar() {
stage('SonarQube analysis') {
prNo = (env.BRANCH_NAME=~/^PR-(\d+)$/)[0][1]
echo "PR-No: ${prNo}"
// requires SonarQube Scanner 2.8+
mvn 'org.jacoco:jacoco-maven-plugin:prepare-agent install -Dmaven.test.failure.ignore=true -Pcoverage-per-test'
withCredentials([[$class: 'StringBinding', credentialsId: 'GITHUB_TOKEN', variable: 'GITHUB_TOKEN']]) {
githubToken=env.GITHUB_TOKEN
withSonarQubeEnv('SonarQube Octodemoapps') {
mvn "-Dsonar.analysis.mode=preview -Dsonar.github.pullRequest=${prNo} -Dsonar.github.oauth=${githubToken} -Dsonar.github.repository=Hyrule/reading-time-app -Dsonar.github.endpoint=https://octodemo.com/api/v3/ org.sonarsource.scanner.maven:sonar-maven-plugin:3.2:sonar"
mvn "org.sonarsource.scanner.maven:sonar-maven-plugin:3.2:sonar"
}
}
context="sonarqube/qualitygate"
setBuildStatus ("${context}", 'Checking Sonarqube quality gate', 'PENDING')
sleep 5
timeout(time: 1, unit: 'MINUTES') { // Just in case something goes wrong, pipeline will be killed after a timeout
def qg = waitForQualityGate() // Reuse taskId previously collected by withSonarQubeEnv
if (qg.status != 'OK') {
setBuildStatus ("${context}", "Sonarqube quality gate fail: ${qg.status}", 'FAILURE')
error "Pipeline aborted due to quality gate failure: ${qg.status}"
} else {
setBuildStatus ("${context}", "Sonarqube quality gate pass: ${qg.status}", 'SUCCESS')
}
}
}
}
def allTests() {
stage 'All tests'
// don't skip anything
mvn 'test -B'
step([$class: 'JUnitResultArchiver', testResults: '**/target/surefire-reports/TEST-*.xml'])
if (currentBuild.result == "UNSTABLE") {
// input "Unit tests are failing, proceed?"
sh "exit 1"
}
}
def preview() {
herokuApp = "${env.HEROKU_PREVIEW}"
deployToStage("preview", herokuApp)
// mvn 'deploy -DskipTests=true'
}
def preProduction() {
switchSnapshotBuildToRelease()
herokuApp = "${env.HEROKU_PREPRODUCTION}"
deployToStage("preproduction", herokuApp)
buildAndPublishToArtifactory()
}
def manualPromotion() {
stage 'Manual Promotion'
// we need a first milestone step so that all jobs entering this stage are tracked an can be aborted if needed
milestone 1
// time out manual approval after ten minutes
timeout(time: 10, unit: 'MINUTES') {
input message: "Does Pre-Production look good?"
}
// this will kill any job which is still in the input step
milestone 2
}
def production() {
herokuApp = "${env.HEROKU_PRODUCTION}"
step([$class: 'ArtifactArchiver', artifacts: '**/target/*.jar', fingerprint: true])
deployToStage("production", herokuApp)
def version = getCurrentHerokuReleaseVersion("${env.HEROKU_PRODUCTION}")
def createdAt = getCurrentHerokuReleaseDate("${env.HEROKU_PRODUCTION}", version)
echo "Release version: ${version}"
createRelease(version, createdAt)
stage ("Promote in Artifactory") {
promoteBuildInArtifactory()
// distributeBuildToBinTray()
}
}
void createRelease(tagName, createdAt) {
withCredentials([[$class: 'StringBinding', credentialsId: 'GITHUB_TOKEN', variable: 'GITHUB_TOKEN']]) {
def body = "**Created at:** ${createdAt}\n**Deployment job:** [${env.BUILD_NUMBER}](${env.BUILD_URL})\n**Environment:** [${env.HEROKU_PRODUCTION}](https://dashboard.heroku.com/apps/${env.HEROKU_PRODUCTION})"
def payload = JsonOutput.toJson(["tag_name": "v${tagName}", "name": "${env.HEROKU_PRODUCTION} - v${tagName}", "body": "${body}"])
def apiUrl = "https://octodemo.com/api/v3/repos/${getRepoSlug()}/releases"
def response = sh(returnStdout: true, script: "curl -s -H \"Authorization: Token ${env.GITHUB_TOKEN}\" -H \"Accept: application/json\" -H \"Content-type: application/json\" -X POST -d '${payload}' ${apiUrl}").trim()
}
}
def deployToStage(stageName, herokuApp) {
stage name: "Deploy to ${stageName}", concurrency: 1
id = createDeployment(getBranch(), "${stageName}", "Deploying branch to ${stageName}")
echo "Deployment ID for ${stageName}: ${id}"
if (id != null) {
setDeploymentStatus(id, "pending", "https://${herokuApp}.herokuapp.com/", "Pending deployment to ${stageName}");
herokuDeploy "${herokuApp}"
setDeploymentStatus(id, "success", "https://${herokuApp}.herokuapp.com/", "Successfully deployed to ${stageName}");
}
}
def herokuDeploy (herokuApp) {
withCredentials([[$class: 'StringBinding', credentialsId: 'HEROKU_API_KEY', variable: 'HEROKU_API_KEY']]) {
mvn "heroku:deploy -DskipTests=true -Dmaven.javadoc.skip=true -B -V -D heroku.appName=${herokuApp}"
}
}
def switchSnapshotBuildToRelease() {
def descriptor = Artifactory.mavenDescriptor()
descriptor.version = '1.0.0'
descriptor.pomFile = 'pom.xml'
descriptor.transform()
}
def buildAndPublishToArtifactory() {
def rtMaven = Artifactory.newMavenBuild()
rtMaven.tool = null
withEnv(["MAVEN_HOME=/usr/share/maven"]) {
rtMaven.deployer releaseRepo:'libs-release-local', snapshotRepo:'libs-snapshot-local', server: server
rtMaven.resolver releaseRepo:'libs-release', snapshotRepo:'libs-snapshot', server: server
rtMaven.run pom: 'pom.xml', goals: 'install', buildInfo: buildInfo
server.publishBuildInfo buildInfo
}
}
def promoteBuildInArtifactory() {
def promotionConfig = [
// Mandatory parameters
'buildName' : buildInfo.name,
'buildNumber' : buildInfo.number,
'targetRepo' : 'libs-prod-local',
// Optional parameters
'comment' : 'deploying to production',
'sourceRepo' : 'libs-release-local',
'status' : 'Released',
'includeDependencies': false,
'copy' : true,
// 'failFast' is true by default.
// Set it to false, if you don't want the promotion to abort upon receiving the first error.
'failFast' : true
]
// Promote build
server.promote promotionConfig
}
def distributeBuildToBinTray() {
def distributionConfig = [
// Mandatory parameters
'buildName' : buildInfo.name,
'buildNumber' : buildInfo.number,
'targetRepo' : 'reading-time-dist',
// Optional parameters
//'publish' : true, // Default: true. If true, artifacts are published when deployed to Bintray.
'overrideExistingFiles' : true, // Default: false. If true, Artifactory overwrites builds already existing in the target path in Bintray.
//'gpgPassphrase' : 'passphrase', // If specified, Artifactory will GPG sign the build deployed to Bintray and apply the specified passphrase.
//'async' : false, // Default: false. If true, the build will be distributed asynchronously. Errors and warnings may be viewed in the Artifactory log.
//"sourceRepos" : ["yum-local"], // An array of local repositories from which build artifacts should be collected.
//'dryRun' : false, // Default: false. If true, distribution is only simulated. No files are actually moved.
]
server.distribute distributionConfig
}
def mvn(args) {
withMaven(
// mavenSettingsConfig: '0e94d6c3-b431-434f-a201-7d7cda7180cb'
//mavenLocalRepo: '/tmp/m2'
) {
// Run the maven build
sh "mvn $args -Dmaven.test.failure.ignore -Dmaven.repo.local=/cache"
}
}
def shareM2(file) {
// Set up a shared Maven repo so we don't need to download all dependencies on every build.
writeFile file: 'settings.xml',
text: "<settings><localRepository>${file}</localRepository></settings>"
}
def getRepoSlug() {
tokens = "${env.JOB_NAME}".tokenize('/')
org = tokens[tokens.size()-3]
repo = tokens[tokens.size()-2]
return "${org}/${repo}"
}
def getBranch() {
tokens = "${env.JOB_NAME}".tokenize('/')
branch = tokens[tokens.size()-1]
return "${branch}"
}
def createDeployment(ref, environment, description) {
withCredentials([[$class: 'StringBinding', credentialsId: 'GITHUB_TOKEN', variable: 'GITHUB_TOKEN']]) {
def payload = JsonOutput.toJson(["ref": "${ref}", "description": "${description}", "environment": "${environment}", "required_contexts": []])
def apiUrl = "https://octodemo.com/api/v3/repos/${getRepoSlug()}/deployments"
def response = sh(returnStdout: true, script: "curl -s -H \"Authorization: Token ${env.GITHUB_TOKEN}\" -H \"Accept: application/json\" -H \"Content-type: application/json\" -X POST -d '${payload}' ${apiUrl}").trim()
def jsonSlurper = new JsonSlurper()
def data = jsonSlurper.parseText("${response}")
return data.id
}
}
void setDeploymentStatus(deploymentId, state, targetUrl, description) {
withCredentials([[$class: 'StringBinding', credentialsId: 'GITHUB_TOKEN', variable: 'GITHUB_TOKEN']]) {
def payload = JsonOutput.toJson(["state": "${state}", "target_url": "${targetUrl}", "description": "${description}"])
def apiUrl = "https://octodemo.com/api/v3/repos/${getRepoSlug()}/deployments/${deploymentId}/statuses"
def response = sh(returnStdout: true, script: "curl -s -H \"Authorization: Token ${env.GITHUB_TOKEN}\" -H \"Accept: application/json\" -H \"Content-type: application/json\" -X POST -d '${payload}' ${apiUrl}").trim()
}
}
void setBuildStatus(context, message, state) {
step([
$class: "GitHubCommitStatusSetter",
contextSource: [$class: "ManuallyEnteredCommitContextSource", context: context],
errorHandlers: [[$class: "ChangingBuildStatusErrorHandler", result: "UNSTABLE"]],
reposSource: [$class: "ManuallyEnteredRepositorySource", url: "https://octodemo.com/${getRepoSlug()}"],
statusResultSource: [ $class: "ConditionalStatusResultSource", results: [[$class: "AnyBuildResult", message: message, state: state]] ]
]);
}
def getCurrentHerokuReleaseVersion(app) {
withCredentials([[$class: 'StringBinding', credentialsId: 'HEROKU_API_KEY', variable: 'HEROKU_API_KEY']]) {
def apiUrl = "https://api.heroku.com/apps/${app}/dynos"
def response = sh(returnStdout: true, script: "curl -s -H \"Authorization: Bearer ${env.HEROKU_API_KEY}\" -H \"Accept: application/vnd.heroku+json; version=3\" -X GET ${apiUrl}").trim()
def jsonSlurper = new JsonSlurper()
def data = jsonSlurper.parseText("${response}")
return data[0].release.version
}
}
def getCurrentHerokuReleaseDate(app, version) {
withCredentials([[$class: 'StringBinding', credentialsId: 'HEROKU_API_KEY', variable: 'HEROKU_API_KEY']]) {
def apiUrl = "https://api.heroku.com/apps/${app}/releases/${version}"
def response = sh(returnStdout: true, script: "curl -s -H \"Authorization: Bearer ${env.HEROKU_API_KEY}\" -H \"Accept: application/vnd.heroku+json; version=3\" -X GET ${apiUrl}").trim()
def jsonSlurper = new JsonSlurper()
def data = jsonSlurper.parseText("${response}")
return data.created_at
}
}
@roman

This comment has been minimized.

Copy link

commented Dec 8, 2017

This example is very helpful, thanks for sharing

@ghost

This comment has been minimized.

Copy link

commented Mar 5, 2018

what do you mean by $ref? branch? commit? a combination of both?

@pabduran

This comment has been minimized.

Copy link

commented May 21, 2018

thanks for sharing!!!

@wender-firmino-aurea

This comment has been minimized.

Copy link

commented May 30, 2018

amazing thanks for share this!

@eltincho89

This comment has been minimized.

Copy link

commented Jun 22, 2018

thanks for sharing!!!

@moh-abk

This comment has been minimized.

Copy link

commented Jul 17, 2018

What does ${env.JOB_NAME} look like? Any help appreciated. Seems it's a url.

@madsop

This comment has been minimized.

Copy link

commented Aug 2, 2018

Thanks for sharing!

@moh-abk: It's a built in Jenkins environment variable. See https://wiki.jenkins.io/display/JENKINS/Building+a+software+project

@KristianWindsor

This comment has been minimized.

Copy link

commented Dec 8, 2018

This is very helpful, thank you.

@almeidh

This comment has been minimized.

Copy link

commented Jan 14, 2019

Solved my problem, thanks for sharing. Highly appreciated.

@bhargavb7

This comment has been minimized.

Copy link

commented May 3, 2019

Where is the 'GITHUB_TOKEN' is saved? Is it in env variable or elsewhere? I have implemented Jenkins running on Kubernetes and I'm trying access value from GITLAB_TOKEN which is a envVars value as part of containerTemplate.

@jonico

This comment has been minimized.

Copy link
Owner Author

commented May 20, 2019

@bhrgavb7: You would need to pass in the GITHUB_TOKEN as a Jenkins environmental variable for the job in question.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.