#!groovy // switch back to a stable tag, after pr 22 is mreged an the next version is released // see https://github.com/cloudogu/ces-build-lib/pull/22 @Library('github.com/cloudogu/ces-build-lib@8e9194e8') import com.cloudogu.ces.cesbuildlib.* node('docker') { mainBranch = 'develop' properties([ // Keep only the last 10 build to preserve space buildDiscarder(logRotator(numToKeepStr: '10')), disableConcurrentBuilds() ]) timeout(activity: true, time: 60, unit: 'MINUTES') { Git git = new Git(this) catchError { Maven mvn = setupMavenBuild() stage('Checkout') { checkout scm } if (isReleaseBranch()) { stage('Set Version') { String releaseVersion = getReleaseVersion(); // set maven versions mvn "versions:set -DgenerateBackupPoms=false -DnewVersion=${releaseVersion}" // set versions for ui packages // we need to run 'yarn install' in order to set version with ui-scripts mvn "-pl :scm-ui buildfrontend:install@install" mvn "-pl :scm-ui buildfrontend:run@set-version" // stage pom changes sh "git status --porcelain | sed s/^...// | grep pom.xml | xargs git add" // stage package.json changes sh "git status --porcelain | sed s/^...// | grep package.json | xargs git add" // stage lerna.json changes sh "git add lerna.json" // commit changes sh "git -c user.name='CES Marvin' -c user.email='cesmarvin@cloudogu.com' commit -m 'release version ${releaseVersion}'" // we need to fetch all branches, so we can checkout master and develop later sh "git config 'remote.origin.fetch' '+refs/heads/*:refs/remotes/origin/*'" sh "git fetch --all" // merge release branch into master sh "git checkout master" sh "git reset --hard origin/master" sh "git merge --ff-only ${env.BRANCH_NAME}" // set tag sh "git -c user.name='CES Marvin' -c user.email='cesmarvin@cloudogu.com' tag -m 'release version ${releaseVersion}' ${releaseVersion}" } } stage('Build') { mvn 'clean install -DskipTests' } parallel( unitTest: { stage('Unit Test') { mvn 'test -DskipFrontendBuild -DskipTypecheck -Pcoverage -pl !scm-it -Dmaven.test.failure.ignore=true' junit allowEmptyResults: true, testResults: '**/target/surefire-reports/TEST-*.xml,**/target/jest-reports/TEST-*.xml' } }, integrationTest: { stage('Integration Test') { mvn 'verify -Pit -DskipUnitTests -pl :scm-webapp,:scm-it -Dmaven.test.failure.ignore=true' junit allowEmptyResults: true, testResults: '**/target/failsafe-reports/TEST-*.xml' } } ) stage('SonarQube') { analyzeWith(mvn) if (!waitForQualityGateWebhookToBeCalled()) { currentBuild.result = 'UNSTABLE' } } if (isMainBranch() || isReleaseBranch()) { stage('Lifecycle') { try { // failBuildOnNetworkError -> so we can catch the exception and neither fail nor make our build unstable nexusPolicyEvaluation iqApplication: selectedApplication('scm'), iqScanPatterns: [[scanPattern: 'scm-server/target/scm-server-app.zip']], iqStage: 'build', failBuildOnNetworkError: true } catch (Exception e) { echo "ERROR: iQ Server policy eval failed. Not marking build unstable for now." echo "ERROR: iQ Server Exception: ${e.getMessage()}" } } if (isBuildSuccessful()) { def commitHash = git.getCommitHash() def imageVersion = mvn.getVersion() if (imageVersion.endsWith('-SNAPSHOT')) { imageVersion = imageVersion.replace('-SNAPSHOT', "-${commitHash.substring(0,7)}-${BUILD_NUMBER}") } stage('Archive') { archiveArtifacts 'scm-webapp/target/scm-webapp.war' archiveArtifacts 'scm-server/target/scm-server-app.*' } stage('Maven Deployment') { // TODO why is the server recreated // delete appassembler target, because the maven plugin fails to recreate the tar sh "rm -rf scm-server/target/appassembler" // deploy java artifacts mvn.useRepositoryCredentials([id: 'maven.scm-manager.org', url: 'https://maven.scm-manager.org/nexus', credentialsId: 'maven.scm-manager.org', type: 'Nexus2']) mvn.deployToNexusRepository() // deploy frontend bits withCredentials([string(credentialsId: 'cesmarvin_npm_token', variable: 'NPM_TOKEN')]) { writeFile encoding: 'UTF-8', file: '.npmrc', text: "//registry.npmjs.org/:_authToken='${NPM_TOKEN}'" writeFile encoding: 'UTF-8', file: '.yarnrc', text: ''' registry "https://registry.npmjs.org/" always-auth true email cesmarvin@cloudogu.com '''.trim() // we are tricking lerna by pretending that we are not a git repository sh "mv .git .git.disabled" try { mvn "-pl :scm-ui buildfrontend:run@deploy" } finally { sh "mv .git.disabled .git" } } } stage('Docker') { docker.withRegistry('', 'hub.docker.com-cesmarvin') { // push to cloudogu repository for internal usage def image = docker.build('cloudogu/scm-manager') image.push(imageVersion) if (isReleaseBranch()) { // push to official repository image = docker.build('scmmanager/scm-manager') image.push(imageVersion) } } } stage('Presentation Environment') { build job: 'scm-manager/next-scm.cloudogu.com', propagate: false, wait: false, parameters: [ string(name: 'changeset', value: commitHash), string(name: 'imageTag', value: imageVersion) ] } if (isReleaseBranch()) { stage('Update Repository') { // merge changes into develop sh "git checkout develop" // TODO what if we have a conflict // e.g.: someone has edited the changelog during the release sh "git merge master" // set versions for maven packages mvn "build-helper:parse-version versions:set -DgenerateBackupPoms=false -DnewVersion='\${parsedVersion.majorVersion}.\${parsedVersion.nextMinorVersion}.0-SNAPSHOT'" // set versions for ui packages mvn "-pl :scm-ui buildfrontend:run@set-version" // stage pom changes sh "git status --porcelain | sed s/^...// | grep pom.xml | xargs git add" // stage package.json changes sh "git status --porcelain | sed s/^...// | grep package.json | xargs git add" // stage lerna.json changes sh "git add lerna.json" // commit changes sh "git -c user.name='CES Marvin' -c user.email='cesmarvin@cloudogu.com' commit -m 'prepare for next development iteration'" // push changes back to remote repository withCredentials([usernamePassword(credentialsId: 'cesmarvin-github', usernameVariable: 'GIT_AUTH_USR', passwordVariable: 'GIT_AUTH_PSW')]) { sh "git -c credential.helper=\"!f() { echo username='\$GIT_AUTH_USR'; echo password='\$GIT_AUTH_PSW'; }; f\" push origin master --tags" sh "git -c credential.helper=\"!f() { echo username='\$GIT_AUTH_USR'; echo password='\$GIT_AUTH_PSW'; }; f\" push origin develop --tags" sh "git -c credential.helper=\"!f() { echo username='\$GIT_AUTH_USR'; echo password='\$GIT_AUTH_PSW'; }; f\" push origin :${env.BRANCH_NAME}" } } } } } } mailIfStatusChanged(git.commitAuthorEmail) } } String mainBranch Maven setupMavenBuild() { Maven mvn = new MavenWrapperInDocker(this, "scmmanager/java-build:11.0.6_10") // disable logging durring the build def logConf = "scm-webapp/src/main/resources/logback.ci.xml" mvn.additionalArgs += " -Dlogback.configurationFile=${logConf}" mvn.additionalArgs += " -Dscm-it.logbackConfiguration=${logConf}" if (isMainBranch() || isReleaseBranch()) { // Release starts javadoc, which takes very long, so do only for certain branches mvn.additionalArgs += ' -DperformRelease' // JDK8 is more strict, we should fix this before the next release. Right now, this is just not the focus, yet. mvn.additionalArgs += ' -Dmaven.javadoc.failOnError=false' } return mvn } void analyzeWith(Maven mvn) { withSonarQubeEnv('sonarcloud.io-scm') { String mvnArgs = "${env.SONAR_MAVEN_GOAL} " + "-Dsonar.host.url=${env.SONAR_HOST_URL} " + "-Dsonar.login=${env.SONAR_AUTH_TOKEN} " if (isPullRequest()) { echo "Analysing SQ in PR mode" mvnArgs += "-Dsonar.pullrequest.base=${env.CHANGE_TARGET} " + "-Dsonar.pullrequest.branch=${env.CHANGE_BRANCH} " + "-Dsonar.pullrequest.key=${env.CHANGE_ID} " + "-Dsonar.pullrequest.provider=bitbucketcloud " + "-Dsonar.pullrequest.bitbucketcloud.owner=sdorra " + "-Dsonar.pullrequest.bitbucketcloud.repository=scm-manager " + "-Dsonar.cpd.exclusions=**/*StoreFactory.java,**/*UserPassword.js " } else { mvnArgs += " -Dsonar.branch.name=${env.BRANCH_NAME} " if (!isMainBranch()) { // Avoid exception "The main branch must not have a target" on main branch mvnArgs += " -Dsonar.branch.target=${mainBranch} " } } mvn "${mvnArgs}" } } boolean isReleaseBranch() { return env.BRANCH_NAME.startsWith("release/"); } String getReleaseVersion() { return env.BRANCH_NAME.substring("release/".length()); } boolean isMainBranch() { return mainBranch.equals(env.BRANCH_NAME) } boolean waitForQualityGateWebhookToBeCalled() { boolean isQualityGateSucceeded = true timeout(time: 5, unit: 'MINUTES') { // Needed when there is no webhook for example def qGate = waitForQualityGate() echo "SonarQube Quality Gate status: ${qGate.status}" if (qGate.status != 'OK') { isQualityGateSucceeded = false } } return isQualityGateSucceeded }