#!groovy /** * Jenkins pipeline to build Corda OS release branches and tags. * PLEASE NOTE: we DO want to run a build for each commit!!! */ @Library('corda-shared-build-pipeline-steps') import com.r3.build.utils.GitUtils GitUtils gitUtils = new GitUtils(this) /** * Sense environment */ boolean isReleaseBranch = (env.BRANCH_NAME =~ /^release\/os\/.*/) boolean isReleaseTag = (env.TAG_NAME =~ /^release-.*(? snykSecurityScan("${env.SNYK_API_KEY}", "--sub-project=$module --configuration-matching='^runtimeClasspath\$' --prune-repeated-subdependencies --debug --target-reference='${env.BRANCH_NAME}' --project-tags=Branch='${env.BRANCH_NAME.replaceAll("[^0-9|a-z|A-Z]+","_")}'") } } } } stage('Generate Snyk License Report') { when { expression { isReleaseTag || isReleaseCandidate || isReleaseBranch } } steps { snykLicenseGeneration(env.SNYK_API_TOKEN, env.C4_OS_SNYK_ORG_ID) } post { always { script { archiveArtifacts artifacts: 'snyk-license-report/*-snyk-license-report.html', allowEmptyArchive: true, fingerprint: true } } } } stage('All Tests') { when { expression { params.DO_TEST } beforeAgent true } parallel { stage('Another agent') { agent { label 'standard' } options { skipDefaultCheckout true } post { always { archiveArtifacts artifacts: '**/*.log', allowEmptyArchive: true, fingerprint: false junit testResults: '**/build/test-results/**/*.xml', keepLongStdio: true, allowEmptyResults: true /* * Copy all JUnit results files into a single top level directory. * This is necessary to stop the allure plugin from hitting out * of memory errors due to being passed many directories with * long paths. * * File names are pre-pended with a prefix when * copied to avoid collisions between files where the same test * classes have run on multiple agents. */ fileOperations([fileCopyOperation( includes: '**/build/test-results/**/*.xml', targetLocation: 'allure-input', flattenFiles: true, renameFiles: true, sourceCaptureExpression: '.*/([^/]+)$', targetNameExpression: 'other-agent-$1')]) stash name: 'allure-input', includes: 'allure-input/**', useDefaultExcludes: false } cleanup { deleteDir() /* clean up our workspace */ } } stages { stage('Unstash') { steps { unstash 'compiled' } } stage('Recompile') { steps { authenticateGradleWrapper() sh script: [ './gradlew', COMMON_GRADLE_PARAMS, 'jar', '--parallel' ].join(' ') } } stage('Unit Test') { steps { sh script: [ './gradlew', COMMON_GRADLE_PARAMS, 'test' ].join(' ') } } stage('Smoke Test') { steps { sh script: [ './gradlew', COMMON_GRADLE_PARAMS, 'smokeTest' ].join(' ') } } stage('Slow Integration Test') { steps { sh script: [ './gradlew', COMMON_GRADLE_PARAMS, 'slowIntegrationTest' ].join(' ') } } } } stage('Same agent') { post { always { archiveArtifacts artifacts: '**/*.log', allowEmptyArchive: true, fingerprint: false junit testResults: '**/build/test-results/**/*.xml', keepLongStdio: true, allowEmptyResults: true /* * Copy all JUnit results files into a single top level directory. * This is necessary to stop the allure plugin from hitting out * of memory errors due to being passed many directories with * long paths. * * File names are pre-pended with a prefix when * copied to avoid collisions between files where the same test * classes have run on multiple agents. */ fileOperations([fileCopyOperation( includes: '**/build/test-results/**/*.xml', targetLocation: 'allure-input', flattenFiles: true, renameFiles: true, sourceCaptureExpression: '.*/([^/]+)$', targetNameExpression: 'same-agent-$1')]) } } stages { stage('Integration Test') { steps { sh script: [ './gradlew', COMMON_GRADLE_PARAMS, 'integrationTest' ].join(' ') } } stage('Deploy Node') { steps { sh script: [ './gradlew', COMMON_GRADLE_PARAMS, 'deployNode' ].join(' ') } } } } } } stage('Publish to Artifactory') { when { expression { isReleaseTag } } steps { rtServer( id: 'R3-Artifactory', url: 'https://software.r3.com/artifactory', credentialsId: 'artifactory-credentials' ) rtGradleDeployer( id: 'deployer', serverId: 'R3-Artifactory', repo: 'corda-releases' ) rtGradleRun( usesPlugin: true, useWrapper: true, switches: '-s --info', tasks: 'artifactoryPublish', deployerId: 'deployer', buildName: env.ARTIFACTORY_BUILD_NAME ) rtPublishBuildInfo( serverId: 'R3-Artifactory', buildName: env.ARTIFACTORY_BUILD_NAME ) } } stage('Publish Release Candidate to Internal Repository') { when { expression { return false} // keeping stage to preserve Jenkins history on release branches, but not supported for patch builds pre 4.9 } steps { withCredentials([ usernamePassword(credentialsId: 'docker-image-pusher-os', usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD') ]) { sh script: [ './gradlew', COMMON_GRADLE_PARAMS, '-Pdocker.image.repository=entdocker.software.r3.com/corda', 'docker:pushDockerImage', '--image OFFICIAL', '--registry-url=entdocker.software.r3.com' ].join(' ') } } } stage('Publish Release to Docker Hub') { when { expression { isReleaseTag && !isInternalRelease && !isReleaseCandidate && !isReleasePatch} } steps { withCredentials([ usernamePassword(credentialsId: 'corda-publisher-docker-hub-credentials', usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD') ]) { sh script: [ './gradlew', COMMON_GRADLE_PARAMS, 'docker:pushDockerImage', '-Pdocker.image.repository=corda/corda', '--image OFFICIAL' ].join(' ') } } } } post { always { script { findBuildScans() if (gitUtils.isReleaseTag()) { gitUtils.getGitLog(env.TAG_NAME, env.GIT_URL.replace('https://github.com/corda/', '')) } try { if (params.DO_TEST) { unstash 'allure-input' allure includeProperties: false, jdk: '', results: [[path: '**/allure-input']] } } catch (err) { echo("Allure report generation failed: $err") if (currentBuild.resultIsBetterOrEqualTo('SUCCESS')) { currentBuild.result = 'UNSTABLE' } } } script { if (!isReleaseTag) { // We want to send a summary email, but want to limit to once per day. // Comparing the dates of the previous and current builds achieves this, // i.e. we will only send an email for the first build on a given day. def prevBuildDate = new Date( currentBuild.previousBuild?.timeInMillis ?: 0).clearTime() def currentBuildDate = new Date( currentBuild.timeInMillis).clearTime() if (prevBuildDate != currentBuildDate) { def statusSymbol = '\u2753' switch(currentBuild.result) { case 'SUCCESS': statusSymbol = '\u2705' break; case 'UNSTABLE': statusSymbol = '\u26A0' break; case 'FAILURE': statusSymbol = '\u274c' break; default: break; } echo('First build for this date, sending summary email') emailext to: '$DEFAULT_RECIPIENTS', subject: "$statusSymbol" + '$BRANCH_NAME regression tests - $BUILD_STATUS', mimeType: 'text/html', body: '${SCRIPT, template="groovy-html.template"}' } else { echo('Already sent summary email today, suppressing') } } } } success { script { sendSlackNotifications("good", "BUILD PASSED", false, "#corda-corda4-open-source-build-notifications") if (isReleaseTag || isReleaseCandidate || isReleaseBranch) { snykSecurityScan.generateHtmlElements() } } } unstable { script { sendSlackNotifications("warning", "BUILD UNSTABLE", false, "#corda-corda4-open-source-build-notifications") if (isReleaseTag || isReleaseCandidate || isReleaseBranch) { snykSecurityScan.generateHtmlElements() } if (isReleaseTag || isReleaseCandidate || isReleaseBranch) { snykSecurityScan.generateHtmlElements() } } } failure { script { sendSlackNotifications("danger", "BUILD FAILURE", true, "#corda-corda4-open-source-build-notifications") if (isReleaseTag || isReleaseBranch || isReleaseCandidate) { sendEmailNotifications("${env.EMAIL_RECIPIENTS}") } } } cleanup { deleteDir() /* clean up our workspace */ } } }