| Thanks for the response, sorry for the cloak and dagger. My pipeline script is a bit unique in that this is what my Jenkinsfile looks like in all my projects:
And then I have a shared library that has a `vars/mavenBuild.groovy` that has the declarative pipeline in it. Pasted below.
import com.broadleafcommerce.jenkins.DependentProject
import com.broadleafcommerce.jenkins.DependentProjectDiscoverer
import com.broadleafcommerce.jenkins.ReleaseUtils
import com.broadleafcommerce.jenkins.SonarUtils
import com.broadleafcommerce.jenkins.Utils
def call(body) {
// evaluate the body block, and collect configuration into the object
def pipelineParams = [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = pipelineParams
body()
println "Building with Jenkinsfile settings: $pipelineParams"
Utils.abortPreviousUpstreamBuilds(currentBuild)
def extraBuildProfiles = pipelineParams.extraBuildProfiles ?: ''
def extraDeployProfiles = pipelineParams.extraDeployProfiles ?: ''
def sonarAnalysis = pipelineParams.sonarAnalysis ?: ''
def javadocsDirectories = pipelineParams.javadocsDirectories ?: []
def referenceDocsDirectories = pipelineParams.referenceDocsDirectories ?: []
def agentLabel = pipelineParams.agentLabel ?: 'maven-jdk8'
def requiresNpmrc = pipelineParams.requiresNpmrc != null ? pipelineParams.requiresNpmrc : false
def forceDeploy = pipelineParams.forceDeploy != null ? pipelineParams.forceDeploy : false
def mainBranch = Utils.isMainBranch(this)
def shouldDeploy = mainBranch || forceDeploy || params.FORCE_DEPLOY || params.IS_RELEASE_BUILD
def mavenVersion = '3.6'
pipeline {
parameters {
string(
// Disabling the classpathURLCheck is for a bug in OpenJDK that prevents Surefire from running properly
// see https://stackoverflow.com/questions/53010200/maven-surefire-could-not-find-forkedbooter-class/53016532
defaultValue: '-Xmx2048m -Djdk.net.URLClassPath.disableClassPathURLCheck=true',
name: 'MAVEN_OPTS',
description: 'JVM parameters passed to Maven as MAVEN_OPTS'
)
string(
// Disabling the classpathURLCheck is for a bug in OpenJDK that prevents Surefire from running properly
// see https://stackoverflow.com/questions/53010200/maven-surefire-could-not-find-forkedbooter-class/53016532
defaultValue: '',
name: 'MAVEN_BUILD_ARGS',
description: 'Arguments passed directly to the Maven process (like -X, -e, any extra -D arguments etc). Only applies to the build phase on CI builds, applies to _both_ release:prepare and release:perform on release builds'
)
booleanParam(
defaultValue: false,
name: 'IS_RELEASE_BUILD',
description: 'Indicate whether or not this build should be treated as a release'
+' (GA, milestone, etc) If you would like to validate the versions that are autogenerated,'
+' use VALIDATE_RELEASE_VERSION_DEFAULTS. Otherwise, current release version (in conjunction'
+' with the RELEASE_BUILD_QUALIFIER) and next SNAPSHOT version will be autodetected.'
)
string(
defaultValue: 'GA',
name: 'RELEASE_BUILD_QUALIFIER',
description: 'Only used in release builds for next and current version calculations'
)
booleanParam(
defaultValue: false,
name: 'FORCE_DEPLOY',
description: 'By default, non-release builds only auto-deploy to Nexus from branches named develop-x.y.'
+' This executes a deploy regardles of what the branch is with whatever version is currently in the pom.'
)
booleanParam(
defaultValue: false,
name: 'VALIDATE_RELEASE_VERSION_DEFAULTS',
description: 'Only used when IS_RELEASE_BUILD is true. Pauses the job and waits for the user to confirm whether or not the autodiscovered version numbers are correct for the release.'
)
}
agent {
label agentLabel
}
options {
buildDiscarder(logRotator(artifactDaysToKeepStr: '1', artifactNumToKeepStr: '', daysToKeepStr: '', numToKeepStr: '15'))
}
stages {
/**
* Common to all build types
*/
stage('Project Validation') {
steps {
script {
def lastCommit = sh returnStdout: true, script: 'git log -1 --pretty=%B'
if (lastCommit.contains("[maven-release-plugin] prepare release")) {
println 'Maven Release build detected, aborting build'
currentBuild.result = 'ABORTED'
return
}
// <scm> must match this repository
def pom = readMavenPom()
def scm = pom.scm
if (!scm) {
error("An <scm> section is required in your pom.xml")
}
if (!scm.connection.startsWith('scm:git:')) {
error("Only git repositories are supported. Ensure that the <scm> section in your pom.xml starts with scm:git:<url>")
}
if (!scm.connection.equalsIgnoreCase("scm:git:${env.GIT_URL}")) {
error("The <scm> section in pom.xml must match the repository being cloned. Expected: scm:git:${env.GIT_URL} but found ${scm.connection}")
}
// mvnw must exist and be executable
def mvnwUnixExists = fileExists 'mvnw'
def mvnwWindowsExists = fileExists 'mvnw.cmd'
if (!mvnwUnixExists || !mvnwWindowsExists) {
error("There must be both mvnw and mvnw.cmd executables at the root of the repository. This can be initialized by executing 'mvn -N io.takari:maven:wrapper'")
}
def mvnwUnixFile = sh returnStdout: true, script: 'ls -l mvnw'.trim()
def mvnwWindowsFile = sh returnStdout: true, script: 'ls -l mvnw.cmd'.trim()
if (!mvnwUnixFile.contains('x') || !mvnwWindowsFile.contains('x')) {
error("The mvnw or mvnw.cmd file is not executable. Run 'chmod +x mvnw mvnw.cmd' in the root of the repository and push the changes to resolve this")
}
if (Utils.isMainBranch(this) && forceDeploy) {
error("The currently executing branch is already a main branch and will be deployed. Remove the forceDeploy parameter from the Jenkinsfile")
}
}
}
}
/**
*
* RELEASE STAGES
*
*/
stage('Prepare release version') {
when { expression { return params.IS_RELEASE_BUILD } }
steps {
milestone null
withMaven(maven: "$mavenVersion", mavenSettingsConfig: 'mvn-settings.xml', mavenOpts: params.MAVEN_OPTS) {
script {
def requestedQualifier = params.RELEASE_BUILD_QUALIFIER
def pom = readMavenPom()
// Set as environment variables to propagate to next stages
env.release = ReleaseUtils.nextReleaseVersion(pom.version, requestedQualifier)
env.nextSnapshot = ReleaseUtils.incrementVersion(pom.version, requestedQualifier)
env.nextRelease = ReleaseUtils.nextReleaseVersion(release, requestedQualifier)
env.tag = pom.artifactId + '-' + release
if (params.VALIDATE_RELEASE_VERSION_DEFAULTS) {
def userInput
timeout(time: 90, unit: 'SECONDS') {
userInput = input(
id: 'userInput', message: 'Release Properties', parameters: [
[$class: 'TextParameterDefinition', defaultValue: release, description: 'The version you are about to release', name: 'release'],
[$class: 'TextParameterDefinition', defaultValue: nextSnapshot, description: 'The next development version that the pom will be changed to after completing the release', name: 'nextSnapshot'],
[$class: 'TextParameterDefinition', defaultValue: tag, description: 'The tag that will show up in the repository for the release', name: 'tag'],
[$class: 'TextParameterDefinition', defaultValue: nextRelease, description: 'What the next release wil be after this one. Used to update milestones and labels automatically on GitHub', name: 'nextRelease']
])
}
env.release = userInput['release']
env.nextSnapshot = userInput['nextSnapshot']
env.nextRelease = userInput['nextRelease']
env.tag = userInput['tag']
}
// this makes commits, need to configure Git
sh 'git config --replace-all user.email em...@domain.com'
sh 'git config --replace-all user.name Git User'
try {
sh "mvn release:prepare ${params.MAVEN_BUILD_ARGS} -DpushChanges=false -Dresume=false -Dtag=$env.tag -DreleaseVersion=$env.release -DdevelopmentVersion=$env.nextSnapshot -DpreparationGoals='clean initialize spotless:apply verify' -DcompletionGoals='spotless:apply' -Darguments='-Ddockerfile.tag=$env.release'"
} catch(Exception e) {
// only rollback if we have .releaseBackup files
def backupFiles = findFiles glob: '*.releaseBackup'
if (backupFiles.length > 0) {
sh "mvn -DpushChanges=false release:rollback -Dtag=$env.tag -DreleaseVersion=$env.release -DdevelopmentVersion=$env.nextSnapshot"
error("The release has failed but it is possible that the repository has already been tagged. The pom has been reverted back but you must manually verify the tag is removed from the repository before retrying to release")
}
throw e
}
}
}
}
}
stage("Perform and deploy the release") {
when { expression { return params.IS_RELEASE_BUILD } }
steps {
milestone null
withMaven(maven: "$mavenVersion", mavenSettingsConfig: 'mvn-settings.xml', mavenOpts: params.MAVEN_OPTS) {
// GPG signing obtained originally from https://wiki.eclipse.org/EE4J_Build#Example_pipeline_build_job_.28for_GPG_signing.29
withCredentials([file(credentialsId: 'gpg', variable: 'KEYRING')]) {
sh 'gpg --batch --import "${KEYRING}"'
sh 'for fpr in $(gpg --list-keys --with-colons | awk -F: \'/fpr:/ {print $10}\' | sort -u); do echo -e "5\ny\n" | gpg --batch --command-fd 0 --expert --edit-key ${fpr} trust; done'
// Perform the Maven deploy
sshagent(['github-ssh']) {
// using -DlocalCheckout makes it so we can prevent a push until the very very end
sh "mvn release:perform ${params.MAVEN_BUILD_ARGS} -Dresume=false -DlocalCheckout=true -Dtag=$env.tag -DreleaseVersion=$env.release -DdevelopmentVersion=$env.nextSnapshot"
}
}
}
// now that the build is on the Nexus, push the changes to the Git repo
sshagent(['github-ssh']) {
sh "git push origin $env.BRANCH_NAME"
sh 'git push origin --tags'
}
}
}
/**
*
* CI STAGES
*
*/
stage('Build') {
when { expression { return !params.IS_RELEASE_BUILD } }
steps {
script {
milestone null
withMaven(maven: "$mavenVersion", mavenSettingsConfig: 'mvn-settings.xml', mavenOpts: params.MAVEN_OPTS) {
withCredentials([string(credentialsId: 'GITHUB_AUTH_TOKEN', variable: 'TOKEN')]) {
def mavenExec = { Map args ->
sh "mvn -U clean verify $args.buildArgs -Pcoverage -Pjavadocs $args.additionalProfiles"
}
def build = requiresNpmrc ? { buildArgs -> withNPM(npmrcConfig: 'npmrc') { mavenExec(buildArgs) }} : mavenExec
def pom = readMavenPom()
DependentProject[] dependentProjects = new DependentProjectDiscoverer().getDependentProjects(pom, TOKEN, this)
if (dependentProjects) {
String modifiedArtifactRoot = mavenDependentBuild(dependentProjects, pom.artifactId, build, [buildArgs: params.MAVEN_BUILD_ARGS, additionalProfiles: extraBuildProfiles])
env.CURRENT_ARTIFACT_ROOT = modifiedArtifactRoot
env.DEPENDENT_CHECKOUT = true
} else {
build([buildArgs: params.MAVEN_BUILD_ARGS, additionalProfiles: extraBuildProfiles])
env.CURRENT_ARTIFACT_ROOT = '.'
}
}
}
}
}
}
stage('Sonar Analysis') {
when { expression { return sonarAnalysis != 'skip' && !params.IS_RELEASE_BUILD } }
steps {
milestone null
withMaven(maven: "$mavenVersion", mavenSettingsConfig: 'mvn-settings.xml', mavenOpts: params.MAVEN_OPTS) {
withSonarQubeEnv('Sonar') {
sh "mvn -f $env.CURRENT_ARTIFACT_ROOT sonar:sonar ${SonarUtils.getAnalysisParameters(this).join(' ')}"
}
}
// It's possible that the analysis succeeds _very_ quickly before it even gets to the next phase
// Need to sleep in-between https://community.sonarsource.com/t/need-a-sleep-between-withsonarqubeenv-and-waitforqualitygate-or-it-spins-in-in-progress/2265/8
echo 'Sleeping for 5 seconds so Sonar can complete the background task'
sleep(5)
}
}
stage('Sonar Quality Gate') {
when { expression { return sonarAnalysis != 'skip' && !params.IS_RELEASE_BUILD } }
steps {
milestone null
timeout(time: 2, unit: 'MINUTES') {
waitForQualityGate abortPipeline: false
}
}
}
stage('Deploy artifact, sources, javadocs to Nexus') {
when { expression { return !params.IS_RELEASE_BUILD } }
steps {
script {
milestone null
// GPG signing obtained originally from https://wiki.eclipse.org/EE4J_Build#Example_pipeline_build_job_.28for_GPG_signing.29
withCredentials([file(credentialsId: 'gpg', variable: 'KEYRING')]) {
sh 'gpg --batch --import "${KEYRING}"'
sh 'for fpr in $(gpg --list-keys --with-colons | awk -F: \'/fpr:/ {print $10}\' | sort -u); do echo -e "5\ny\n" | gpg --batch --command-fd 0 --expert --edit-key ${fpr} trust; done'
withMaven(maven: "$mavenVersion", mavenSettingsConfig: 'mvn-settings.xml', mavenOpts: params.MAVEN_OPTS) {
def dockerTag = Utils.getDistTag(this, params.IS_RELEASE_BUILD)
// not a normal main branch, need to do some transformation
if (!Utils.isMainBranch(this)) {
def pom = readMavenPom()
def branchSpecificVersion = Utils.getBranchSpecificVersion(this, pom.version)
println "Transforming version from $pom.version to a branch-specific version $branchSpecificVersion"
sh "mvn -f $env.CURRENT_ARTIFACT_ROOT versions:set -DnewVersion=${branchSpecificVersion} -DgenerateBackupPoms"
dockerTag = "branch-${Utils.getSanitizedBranchName(this)}-latest-snapshot"
}
sh "mvn -f $env.CURRENT_ARTIFACT_ROOT -Danimal.sniffer.skip=true -DskipTests -DdeployAtEnd=true -Psources -Pjavadocs -Pdocs -Psign-artifacts -Ddockerfile.tag=${dockerTag} $extraDeployProfiles deploy"
}
}
}
}
}
/**
*
* Common final stages
*
*/
stage('Upload Javadocs site') {
when { expression { return javadocsDirectories && shouldDeploy } }
steps {
milestone null
script {
def prefix = params.IS_RELEASE_BUILD ? 'target/checkout/' : ''
javadocsDirectories.each {
uploadDocsFile("${prefix}${it}/*-javadoc.jar", 'javadocs')
}
}
}
}
stage('Upload Reference docs site') {
when { expression { return referenceDocsDirectories && shouldDeploy } }
steps {
milestone null
script {
def prefix = params.IS_RELEASE_BUILD ? 'target/checkout/' : ''
referenceDocsDirectories.each {
uploadDocsFile("${prefix}${it}/*-reference.zip", 'docs')
}
}
}
}
}
post {
failure {
slackFailureNotification()
}
success {
slackSuccessNotification()
supersedeBranchCheck()
}
}
}
}
def uploadDocsFile(def file, def host) {
withCredentials([
sshUserPrivateKey(credentialsId: "docs-upload-ssh", keyFileVariable: 'keyfile')
]) {
// ignore host checking
sh "scp -i ${keyfile} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${file} jenkins@${host}:"
sh "ssh -i ${keyfile} -t -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null jenkins@${host} './extractDocs.sh'"
}
}
|