Last active
November 11, 2019 13:57
Basic Jenkinsfile that use a external configuration as pipeline mapping
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!groovy | |
import groovy.json.JsonOutput | |
import groovy.json.JsonSlurper | |
import hudson.model.Result | |
import hudson.model.Run | |
import jenkins.model.CauseOfInterruption.UserInterruption | |
/* | |
Jenkins version: 2.150.2 | |
Github API version: v3 | |
Depend on: https://gist.github.com/JD10NN3/cb44137a94111cf9693c7815d26fa919 | |
Part of: https://medium.com/faun/externalize-the-configuration-of-the-jenkins-pipeline-flow-8f326c7cc9c4 | |
*/ | |
// Our Github endpoints and repositories | |
// *Change the domain name to make it work with Github Enterprise. | |
GITHUB_DOMAIN_NAME = "github.com" | |
GITHUB_ENDPOINT_API = "${GITHUB_DOMAIN_NAME}/api/v3" | |
GITHUB_MY_PROJECT_REPO = "https://${GITHUB_ENDPOINT_API}/repos/JD10NN3/MyProject" | |
// This is where we host our external configuration files. | |
// Note: You must adjust this variable to point an existing repository... | |
GITHUB_SCRIPTS_AND_CONFIGURATION_CONTENTS_URL = "https://${GITHUB_ENDPOINT_API}/repos/JD10NN3/Scripts-and-Configurations/contents" | |
// The location of the mapping configuration | |
// Refer to that Gist to see the content of mapping.groovy: | |
// https://gist.github.com/JD10NN3/cb44137a94111cf9693c7815d26fa919 | |
// Note: You must adjust this variable to point your mapping configuration... | |
GITHUB_MAPPING_URL = "${GITHUB_SCRIPTS_AND_CONFIGURATION_CONTENTS_URL}/pipeline/src/mapping.groovy" | |
// The location of the triggers configuration | |
// Refer to that Gist to see the content of mapping.groovy: | |
// https://gist.github.com/JD10NN3/5de45a5c9b75c25c81da107acdda34c8 | |
// Note: You must adjust this variable to point your triggers configuration... | |
GITHUB_TRIGGERS_URL = "${GITHUB_SCRIPTS_AND_CONFIGURATION_CONTENTS_URL}/pipeline/src/triggers.groovy" | |
// This is where we fetch our external configuration (mapping and triggers) used to | |
// determine the steps that are required to run inside the pipeline. We are hosting | |
// the configuration inside a separate repository to allow us to apply different | |
// rights for devops purposes. | |
def mapping = null | |
def triggers = null | |
node { | |
// Wrap the call inside the withCredentials to avoid showing in clear text sensible datas. | |
// eg.: passwords, tokens... etc | |
withCredentials([string(credentialsId: 'github-token', variable: 'GITHUB_ACCESS_TOKEN')]) { | |
// We fetch the pipeline mapping and assign it to our mapping variable. | |
// This is going to be used later to determine the steps to perform and with which options. | |
def response = GITHUB_MAPPING_URL.toURL().getText(requestProperties: ['Authorization': "token ${GITHUB_ACCESS_TOKEN}", 'Accept': "application/vnd.github.v3.raw"]) | |
mapping = evaluate(response) | |
// We fetch the pipeline triggers. | |
// The triggers fetched are going to be used to launch actions at specific moment by the pipeline. | |
// eg.: on_commit, on_approval | |
response = GITHUB_TRIGGERS_URL.toURL().getText(requestProperties: ['Authorization': "token ${GITHUB_ACCESS_TOKEN}", 'Accept': "application/vnd.github.v3.raw"]) | |
triggers = evaluate(response) | |
} | |
} | |
// Global variables | |
def workspaceDirectory = "" | |
def pipeline = ["branch": env.BRANCH_NAME, "targets": mapping.getFor(env.BRANCH_NAME)] | |
boolean isMultiTargets = pipeline.targets.size() > 1 | |
boolean checkoutDone = false | |
// | |
def handleCheckout = { | |
checkout([ | |
$class : 'GitSCM', | |
branches : scm.branches, | |
extensions : [ | |
[$class: 'PruneStaleBranch'], | |
[$class: 'CleanCheckout'] | |
], | |
userRemoteConfigs: scm.userRemoteConfigs | |
]) | |
} | |
// This must be adjusted to meet the specific project criterias | |
properties([[$class: 'ThrottleJobProperty', categories: [], limitOneJobWithMatchingParams: false, maxConcurrentPerNode: 0, | |
maxConcurrentTotal: 0, paramsToUseForLimit: '', throttleEnabled: false, throttleOption: 'project'], | |
[$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', artifactDaysToKeepStr: '', | |
artifactNumToKeepStr: '1', daysToKeepStr: '', numToKeepStr: '3']], | |
[$class: 'GithubProjectProperty', displayName: 'IdentityIQ', | |
projectUrlStr: 'https://github.com/JD10NN3/MyProject/'], pipelineTriggers([])]) | |
try { | |
// We iterate through each targeted environment (intg, uat, prep, prod...) | |
// provided by our pipeline configuration. | |
// Take a look at the mapping.groovy for a better understanding | |
pipeline.targets.each { target -> | |
// | |
def stageSuffix = isMultiTargets ? ' (' + target.env + ')' : '' | |
// Does the "checkout" step must be executed ? | |
// If true, only checkout if not done previously | |
// (we don't want to checkout a branch two times in the same pipeline for no reason) | |
if (target.stages.containsKey('checkout') && !checkoutDone) { | |
stage('Checkout') { | |
node { | |
workspaceDirectory = env.WORKSPACE | |
// time to checkout the sources | |
handleCheckout() | |
checkoutDone = true | |
// Let's do a call to the "on_commit" trigger for the current branch. | |
withCredentials([string(credentialsId: 'github-token', variable: 'GITHUB_ACCESS_TOKEN')]) { | |
triggers.callTriggerOnBranch("on_commit", env.BRANCH_NAME) | |
} | |
} | |
} | |
} | |
// Does the "build" step must be executed ? | |
if (target.stages.containsKey('build')) { | |
// Options provided from the pipeline mapping | |
def buildOptions = target.stages.build | |
stage('Build' + stageSuffix) { | |
milestone() | |
node { | |
dir(workspaceDirectory) { | |
/* | |
This is where you should provide the commands | |
needed as part of the Build step. | |
eg.: Clean, Build, Archiving... | |
*/ | |
// Example for the build command | |
// target.env : contains the name of the environment for which | |
// we are currently building the package | |
def command = buildOptions.getOrDefault("command", "war") | |
sh "\$GRADLE_HOME/bin/gradle --no-daemon ${command} -DtargetedEnv=${target.env}" | |
// Example for the archive command | |
// Are we archiving the deployment package ? | |
if (buildOptions.getOrDefault("archive", true)) { | |
zip archive: true, dir: 'build/deployment', glob: '', zipFile: target.env + '_deployment_package.zip' | |
} | |
// delete file to save space and avoid concurrent build to | |
// complain because the file already exist | |
if (fileExists(target.env + '_deployment_package.zip')) { | |
sh 'rm -f ' + target.env + '_deployment_package.zip' | |
} | |
} | |
} | |
} | |
} | |
// Does the "tests" step must be executed ? | |
if (target.stages.containsKey('tests')) { | |
stage('Tests' + stageSuffix) { | |
milestone() | |
node { | |
dir(workspaceDirectory) { | |
/* | |
This is where you should provide the commands | |
needed as part of the Test step. | |
eg.: Run tests, Report results... | |
*/ | |
} | |
} | |
} | |
} | |
// Does the "deploy" step must be executed ? | |
if (target.stages.containsKey('deploy')) { | |
// Options provided from the pipeline mapping | |
def deployAttributes = target.stages.deploy | |
// Does an approval is required to deploy to the targeted environment ? | |
// This approval allow us to confirm with other developers or testers | |
// whether or not the environment is currently used before launching the deployment. | |
if (deployAttributes.getOrDefault("approval", false) || currentBuild.result == "UNSTABLE") { | |
stage('Approval' + stageSuffix) { | |
milestone() | |
// If an approval is required before deployment, we ask for. | |
if (deployAttributes.getOrDefault("approval", false)) { | |
input "Do you approve deployment in ${target.env}?" | |
} | |
// Let's do a call to the "on_approval" trigger for the current branch. | |
// Note: we can provide arguments... (eg.: target) | |
withCredentials([string(credentialsId: 'github-token', variable: 'GITHUB_ACCESS_TOKEN')]) { | |
triggers.callTriggerOnBranch("on_approval", env.BRANCH_NAME, target) | |
} | |
// Second approval in case the build is unstable... | |
if (currentBuild.result == "UNSTABLE") { | |
input "Based on the previous steps, this version seems UNSTABLE, do you still want to deploy on '${target.env}'?" | |
} | |
milestone() | |
} | |
} | |
// Lock the target environment to avoid doing multiple deployment at the same time | |
lock(resource: target.env + '-server', inversePrecedence: true) { | |
stage('Deploy' + stageSuffix) { | |
milestone() | |
node { | |
// Change the folder path to meet your specific project criterias. | |
dir(workspaceDirectory + '/build/deploy') { | |
/* | |
This is where you should provide the commands | |
needed as part of the Deployment step. | |
*/ | |
} | |
} | |
} | |
} | |
} | |
} | |
} catch (error) { | |
// Due to an error, we set the current build result to the proper state. | |
currentBuild.result = 'FAILURE' | |
throw error | |
} finally { | |
// Do nothing and let Jenkins manage the current build result. | |
// This would be a nice place to add Github check statuses updates. | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment