import java.util.regex.Matcher /** * Pipeline to release a stable version of Silverpeas from a given build version. The first step is * to figure out the version to release and from which SCM branch the release has to be performed. * The pipeline identifies the release of either a patch version from the current stable branch or a * major/minor version from the master branch. It takes also in charge the release of a patch * version from a previous stable branch other than the current one. * * In order to get the build version, the pipeline requires the Jenkins job aimed at constructing * such build version to be named 'Silverpeas_[PROJECT_TYPE]_AutoDeploy' with [PROJECT_TYPE] being * either 'Master' for the main major or minor version of Silverpeas in development, or 'Stable' for * the next patch version in development of the last stable version of Silverpeas, or a branch name * for older stable branch in which patch versions are yet ongoing. * * Once the release is successfully done, the pipeline triggers extra jobs that must exist: * 'Silverpeas IzPack Installer' to publish an IzPack installer of the new version of * Silverpeas. * 'Silverpeas Dev Image' to publish a Docker image of a dev environment for working * on the new stable branch of Silverpeas. * 'Silverpeas Projects Build Image' to publish a Docker image for building a Silverpeas * project. * 'Silverpeas Test Image' to publish a Docker image of the new version of Silverpeas * for testing purpose. * 'Silverpeas Prod Image' to publish a production-ready Docker image of the new * version of Silverpeas. * 'Silverpeas_Documentation_Publishing' to publish the documentation of the new version of * Silverpeas. * * For doing, this pipeline expects the following environment variables to be set: * STABLE_BRANCH the SCM branch in which the current stable version of the project is currently * maintained * * This pipeline requires the following parameters: * BUILD_VERSION the build version of Silverpeas from which the release has to be done. * * The build is performed within a dedicated Docker image in order to ensure the reproducibility of * the builds and to containerize them from the host OS. */ // versions of the Docker image to use with the different SCM branch names of the projects def imageVersions = [ '6.1.x' : '6.1', '6.2.x' : '6.2.1', '6.3.x' : '6.3.3', '6.4.x' : '6.4', 'master' : 'latest' ] String imageVersion = getDockerImageVersion(imageVersions) // projects making Silverpeas to release def projects = [ core : 'Silverpeas-Core', components : 'Silverpeas-Components', looks : 'Silverpeas-Looks', setup : 'Silverpeas-Setup', distribution: 'Silverpeas-Distribution', assembly : 'Silverpeas-Assembly', mobile : 'silverpeasmobile'] pipeline { agent { docker { image "silverpeas/silverbuild:${imageVersion}" args ''' -v $HOME/.m2/settings.xml:/home/silverbuild/.m2/settings.xml -v $HOME/.m2/settings-security.xml:/home/silverbuild/.m2/settings-security.xml -v $HOME/.gitconfig:/home/silverbuild/.gitconfig -v $HOME/.ssh:/home/silverbuild/.ssh -v $HOME/.gnupg:/home/silverbuild/.gnupg ''' } } parameters { string( description: 'The build version of Silverpeas from which the release has to be done', name: 'BUILD_VERSION' ) } environment { gitBaseRepo = 'https://github.com/Silverpeas/' gitCredential = 'cacc0467-7c85-41d1-bf4e-eaa470dd5e59' mavenRepo = 'https://nexus3.silverpeas.org/repository/silverpeas' branch = getBranch() isPatchVersion = isPatchVersion() wildflyVersion = '' jackrabbitVersion = null nextVersion = '' pomParentVersion = '' buildToRelease = null artifact = 'target/release.yaml' } stages { stage('Prepare the release') { steps { script { sh 'rm -rf target && mkdir target' String type = getBranchType() copyArtifacts projectName: "Silverpeas_${type}_AutoDeploy", flatten: true, selector: specific(params.BUILD_VERSION) buildToRelease = readYaml file: 'build.yaml' nextVersion = getNextVersion(buildToRelease.release) sh 'rm -f build.yaml' copyArtifacts projectName: "Silverpeas_Mobile_${type}_AutoDeploy", flatten: true, selector: specific(params.BUILD_VERSION) def mobileToRelease = readYaml file: 'build.yaml' buildToRelease.commits['silverpeasmobile'] = mobileToRelease.commit sh 'rm -f build.yaml' if (branch != 'master') { sh "curl -fsSL -o pom.xml https://raw.githubusercontent.com/Silverpeas/Silverpeas-Assembly/${branch}/pom.xml" def pom = readMavenPom() jackrabbitVersion = pom.properties['jackrabbit.version'] sh 'rm -f pom.xml' } } } } stage('Check the build version can be used for release') { when { expression { !buildToRelease.tested } } steps { error("The build ${buildToRelease.version} cannot be used for the release of ${buildToRelease.release} as it hasn't been tested or no QA has been applied on it!") } } stage('Check Jackrabbit version') { when { expression { branch != 'master' && jackrabbitVersion != null && (jackrabbitVersion.contains('build') || jackrabbitVersion.contains('SNAPSHOT')) } } steps { error("The Jackrabbit JCA dependency must be at a stable version for this project to be released. Current version is ${jackrabbitVersion}") } } stage ('Check Silverpeas Projects Definition') { steps { script { String parentVersion = buildToRelease.parent buildToRelease.releaseParent = parentVersion != null && branch == 'master' && (parentVersion.contains('build') || parentVersion.contains('SNAPSHOT')) sh "curl -fsSL -o pom.xml ${mavenRepo}/org/silverpeas/silverpeas-project/${parentVersion}/silverpeas-project-${parentVersion}.pom" def pom = readMavenPom() wildflyVersion = pom.properties['wildfly.version'] sh 'rm -f pom.xml' } } } stage('Echo release parameters') { steps { echo """ Release processing parameters: Version to release ${buildToRelease.release} From build version ${params.BUILD_VERSION} Patch version ${isPatchVersion} Wildfly version ${wildflyVersion} POM parent version ${buildToRelease.parent} Should release POM parent? ${buildToRelease.releaseParent} Next version ${nextVersion} """ } } stage('Release Silverpeas Projects Definition') { when { expression { buildToRelease.releaseParent } } steps { build job: 'Silverpeas_Project_Definition_Release', propagate: true, wait: true copyArtifacts projectName: 'Silverpeas_Project_Definition_AutoDeploy', flatten: true script { def parentBuild = readYaml file: 'build.yaml' buildToRelease.parent = parentBuild.release } sh 'rm -f build.yaml' } } stage('Release Silverpeas Core') { steps { dir(projects.core) { git credentialsId: gitCredential, branch: branch, url: (gitBaseRepo + projects.core) releaseProject project: projects.core, commit: buildToRelease.commits[projects.core], version: buildToRelease.release, parent: buildToRelease.parent, isPatch: isPatchVersion } } } stage('Prepare next version of Silverpeas Core') { steps { dir(projects.core) { prepareNextVersion project: projects.core, commit: buildToRelease.commits[projects.core], version: buildToRelease.release, nextVersion: nextVersion, isPatch: isPatchVersion } } } stage('Release Silverpeas Components') { steps { dir(projects.components) { git credentialsId: gitCredential, branch: branch, url: (gitBaseRepo + projects.components) releaseProject project: projects.components, commit: buildToRelease.commits[projects.components], version: buildToRelease.release, parent: buildToRelease.parent, isPatch: isPatchVersion } } } stage('Prepare next version of Silverpeas Components') { steps { dir(projects.components) { prepareNextVersion project: projects.components, commit: buildToRelease.commits[projects.components], version: buildToRelease.release, nextVersion: nextVersion, isPatch: isPatchVersion } } } stage('Release Silverpeas Assembly') { steps { dir (projects.assembly) { git credentialsId: gitCredential, branch: branch, url: (gitBaseRepo + projects.assembly) releaseProject project: projects.assembly, commit: buildToRelease.commits[projects.assembly], version: buildToRelease.release, parent: buildToRelease.parent, isPatch: isPatchVersion } } } stage('Prepare next version of Silverpeas Assembly') { steps { dir (projects.assembly) { prepareNextVersion project: projects.assembly, commit: buildToRelease.commits[projects.assembly], version: buildToRelease.release, nextVersion: nextVersion, isPatch: isPatchVersion } } } stage('Release Silverpeas Setup') { steps { dir(projects.setup) { git credentialsId: gitCredential, branch: branch, url: (gitBaseRepo + projects.setup) releaseProject project: projects.setup, commit: buildToRelease.commits[projects.setup], version: buildToRelease.release, parent: buildToRelease.parent, isPatch: isPatchVersion } } } stage('Prepare next version of Silverpeas Setup') { steps { dir(projects.setup) { prepareNextVersion project: projects.setup, commit: buildToRelease.commits[projects.setup], version: buildToRelease.release, nextVersion: nextVersion, isPatch: isPatchVersion } } } stage('Release Silverpeas Distribution') { steps { dir(projects.distribution) { git credentialsId: gitCredential, branch: branch, url: (gitBaseRepo + projects.distribution) releaseProject project: projects.distribution, commit: buildToRelease.commits[projects.distribution], version: buildToRelease.release, parent: buildToRelease.parent, isPatch: isPatchVersion } } } stage('Prepare next version of Silverpeas Distribution') { steps { dir(projects.distribution) { prepareNextVersion project: projects.distribution, commit: buildToRelease.commits[projects.distribution], version: buildToRelease.release, nextVersion: nextVersion, isPatch: isPatchVersion } } } stage('Release Silverpeas Looks') { steps { dir(projects.looks) { git credentialsId: gitCredential, branch: branch, url: (gitBaseRepo + projects.looks) releaseProject project: projects.looks, commit: buildToRelease.commits[projects.looks], version: buildToRelease.release, parent: buildToRelease.parent, isPatch: isPatchVersion } } } stage('Prepare next version of Silverpeas Looks') { steps { dir(projects.looks) { prepareNextVersion project: projects.looks, commit: buildToRelease.commits[projects.looks], version: buildToRelease.release, nextVersion: nextVersion, isPatch: isPatchVersion } } } stage('Release Silverpeas Mobile') { steps { dir(projects.mobile) { git credentialsId: gitCredential, branch: branch, url: (gitBaseRepo + projects.mobile) releaseProject project: projects.mobile, commit: buildToRelease.commits[projects.mobile], version: buildToRelease.release, parent: buildToRelease.parent, isPatch: isPatchVersion } } } stage('Prepare next version of Silverpeas Mobile') { steps { dir(projects.mobile) { prepareNextVersion project: projects.mobile, commit: buildToRelease.commits[projects.mobile], version: buildToRelease.release, nextVersion: nextVersion, isPatch: isPatchVersion } } } stage('Publish Docker Image for development environment') { when { expression { !isPatchVersion } } steps { build job: 'Silverpeas Dev Image', parameters: [ string(name: 'IMAGE_VERSION', value: buildToRelease.release), string(name: 'WILDFLY_VERSION', value: wildflyVersion.replace(".Final", "")) ], wait: false } } stage('Publish Docker image for Jenkins job') { when { expression { !isPatchVersion } } steps { build job: 'Silverpeas Projects Build Image', parameters: [ string(name: 'IMAGE_VERSION', value: buildToRelease.release), string(name: 'WILDFLY_VERSION', value: wildflyVersion.replace(".Final", "")) ], wait: false } } stage('Publish Docker Image of Silverpeas for trial purpose') { steps { build job: 'Silverpeas Test Image', parameters: [ string(name: 'SILVERPEAS_VERSION', value: buildToRelease.release), string(name: 'WILDFLY_VERSION', value: wildflyVersion.replace(".Final", "")) ], wait: false } } stage('Prepare Docker Image of Silverpeas for production') { steps { build job: 'Silverpeas Prod Image', parameters: [ string(name: 'SILVERPEAS_VERSION', value: buildToRelease.release), string(name: 'WILDFLY_VERSION', value: wildflyVersion.replace(".Final", "")) ], wait: false } } stage('Publish Silverpeas Documentation') { steps { echo 'Trigger the documentation generation and publishing job' build job: 'Silverpeas_Documentation_Publishing', parameters: [ string(name: 'SILVERPEAS_VERSION', value: buildToRelease.release) ], quietPeriod: 20, wait: false } } stage('Create YAML release report') { steps { script { writeYaml file: artifact, data: ['release': buildToRelease.release, 'parent': buildToRelease.parent, 'wildfly': wildflyVersion, 'build': params.BUILD_VERSION, 'branch': branch] } } } } post { success { script { currentBuild.displayName = buildToRelease.release } archiveArtifacts artifacts: artifact, fingerprint: true script { if (branch == 'master') { emailext( subject: "Silverpeas ${buildToRelease.release} has been released!", body: """ Enjoy this new version of Silverpeas, nevertheless don't forget to update in Jenkins the following environment variables in order the usual jobs in Jenkins continues to work properly: STABLE_BRANCH to '${buildToRelease.release}.x' IMAGE_FOR_STABLE to '${buildToRelease}' """, to: 'miguel.moquillon@silverpeas.org,' + 'david.lesimple@silverpeas.com,sebastien.vuillet@silverpeas.com,' + 'silveryocha@chastagnier.com' ) } } } always { step([$class : 'Mailer', notifyEveryUnstableBuild: true, recipients : "david.lesimple@silverpeas.org, sebastien.vuillet@silverpeas.org, aurore.allibe@silverpeas.org, miguel.moquillon@silverpeas.org, silveryocha@chastagnier.com", sendToIndividuals : true]) } } } void withGit( closure) { withCredentials([usernamePassword(credentialsId: gitCredential, usernameVariable: 'GIT_AUTH_USR', passwordVariable: 'GIT_AUTH_PSW')]) { sh 'git config --local credential.helper "!f() { echo username=\\$GIT_AUTH_USR; echo password=\\$GIT_AUTH_PSW; }; f"' closure.call() } } void releaseProject(args) { // we first change the Maven repo definition to use the repo dedicated to releases // then we release the project and finally we tag it sh """ git checkout ${args.commit} git checkout -b release-${args.version} """ if (fileExists('pom.xml')) { publishVersionOfMavenProject(args) } else { publishVersionOfGradleProject(args) } withGit { sh """ git commit -am "Release of ${args.version} from ${params.BUILD_VERSION} (commit ${args.commit})" git tag ${args.version} git push origin --tags """ } } void publishVersionOfMavenProject(args) { def pom = readMavenPom() if (pom.parent && args.parent && pom.parent.version != args.parent) { sh """ mvn -U versions:update-parent -DgenerateBackupPoms=false -DparentVersion="[${args.parent}]" git commit -am "Update parent POM to version ${args.parent}" """ } if (fileExists('Jenkinsfile') && args.isPatch == 'false') { sh """ sed -i -e "s/silverpeas\\/silverbuild'/silverpeas\\/silverbuild:${args.version}'/g" Jenkinsfile """ } sh """ sed -i -e "s/[0-9a-zA-Z.-]\\+/${args.version}/g" pom.xml sed -i -e "s/[0-9a-zA-Z.-]\\+/${args.version}/g" pom.xml sed -i -e "s/[a-zA-Z0-9.\\-]\\+/${args.version}/g" pom.xml mvn -U versions:set -DgenerateBackupPoms=false -DnewVersion=${args.version} mvn clean deploy -Dmaven.test.skip=true -Pdeployment -Prelease-sign-artifacts """ } void publishVersionOfGradleProject(args) { if (fileExists("Jenkinsfile") && args.isPatch == 'false') { sh """ sed -i -e "s/silverpeas\\/silverbuild'/silverpeas\\/silverbuild:${args.version}'/g" Jenkinsfile """ } sh """ sed -i -e "s/version = '.\\+'/version = '${args.version}'/g" build.gradle ./gradlew clean build publish """ } void prepareNextVersion(args) { if (args.isPatch == 'false') { echo "Create bug-fix branch ${args.version}.x" createMinorBranch(args) } sh "git checkout ${branch}" if (fileExists('pom.xml')) { buildSnapshotVersionOfMavenProject(args.nextVersion) } else { buildSnapshotVersionOfGradleProject(args.nextVersion) } withGit { sh """ git commit -am "${args.version} has been released from build ${params.BUILD_VERSION} (${args.commit}).\\ Prepare for development iteration of next version ${args.nextVersion}" git push origin ${branch} """ } } void createMinorBranch(args) { sh "git checkout -b ${args.version}.x" if (fileExists('pom.xml')) { buildSnapshotVersionOfMavenProject("${args.version}.1") } else if (fileExists('build.gradle')) { buildSnapshotVersionOfGradleProject("${args.version}.1") } withGit { String anyModif = sh(script: 'git status -s', returnStdout: true).trim() if (!anyModif.isEmpty()) { sh "git commit -am \"Prepare branch ${args.version}.x for bug fixes\"" } sh "git push origin HEAD:${args.version}.x" } } void buildSnapshotVersionOfMavenProject(version) { sh """ sed -i -e "s/[0-9a-zA-Z.-]\\+/${version}-SNAPSHOT/g" pom.xml sed -i -e "s/[0-9a-zA-Z.-]\\+/${version}-SNAPSHOT/g" pom.xml sed -i -e "s/[0-9.]\\+/${version}/g" pom.xml sed -i -e "s/[a-zA-Z0-9.\\-]\\+/HEAD/g" pom.xml mvn -U versions:set -DgenerateBackupPoms=false -DnewVersion=${version}-SNAPSHOT mvn clean install -Dmaven.test.skip=true -Djava.awt.headless=true """ } void buildSnapshotVersionOfGradleProject(version) { sh """ sed -i -e "s/version = '.\\+'/version = '${version}-SNAPSHOT'/g" build.gradle ./gradlew clean build publish """ } String getNextVersion(version) { Matcher m = version =~ /^([0-9.]+)-rc(\d+)/ String nextVersion if (m.matches()) { nextVersion = "${m.group(1)}-rc${(m.group(2) as Integer) + 1}" } else { String[] parts = version.split('\\.') nextVersion = parts.length < 3 ? "${parts[0]}.${(parts[1] as Integer) + 1}" : "${parts[0]}.${parts[1]}.${(parts[2] as Integer) + 1}" } return nextVersion } String getBranchType() { String type switch (branch) { case env.STABLE_BRANCH: type = 'Stable' break case 'master': type = 'Master' break default: type = branch break } return type } String getBranch() { Matcher matcher = params.BUILD_VERSION =~ '^(\\d+.\\d+)\\..*$' matcher ? "${matcher[0][1]}.x" : 'master' } boolean isPatchVersion() { Matcher m = params.BUILD_VERSION =~ /^\d\.\d\.\d-build\d+$/ return m.matches() } String getDockerImageVersion(imageVersions) { String currentBranch = getBranch() Matcher matcher = currentBranch =~ '^(\\d+.\\d+\\..*)$' matcher ? imageVersions["${matcher[0][1]}"] : imageVersions[currentBranch] }