corda/.ci/dev/regression/Jenkinsfile

440 lines
18 KiB
Plaintext
Raw Normal View History

#!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
import com.r3.build.enums.SnykOrganisation
import com.r3.build.utils.SnykUtils
GitUtils gitUtils = new GitUtils(this)
SnykUtils snykUtils = new SnykUtils(this)
/**
* Sense environment
*/
boolean isReleaseBranch = (env.BRANCH_NAME =~ /^release\/os\/.*/)
2020-06-20 10:08:52 +00:00
boolean isReleaseTag = (env.TAG_NAME =~ /^release-.*(?<!_JDK11)$/)
boolean isInternalRelease = (env.TAG_NAME =~ /^internal-release-.*$/)
boolean isReleaseCandidate = (env.TAG_NAME =~ /^(release-.*(RC|HC).*(?<!_JDK11))$/)
def buildEdition = (isReleaseTag || isReleaseCandidate) ? "Corda Community Edition" : "Corda Open Source"
/**
* Common Gradle arguments for all Gradle executions
*/
String COMMON_GRADLE_PARAMS = [
'--no-daemon',
'--stacktrace',
'--info',
'-Pcompilation.warningsAsErrors=false',
'-Ptests.failFast=true',
'--build-cache',
'-DexcludeShell'
].join(' ')
pipeline {
agent { label 'standard' }
/*
* List options in alphabetical order
*/
options {
buildDiscarder(logRotator(daysToKeepStr: '14', artifactDaysToKeepStr: '14'))
parallelsAlwaysFailFast()
timeout(time: 6, unit: 'HOURS')
timestamps()
}
parameters {
booleanParam defaultValue: true, description: 'Run tests during this build?', name: 'DO_TEST'
}
/*
* List environment variables in alphabetical order
*/
environment {
ARTIFACTORY_BUILD_NAME = "Corda :: Publish :: Publish Release to Artifactory :: ${env.BRANCH_NAME}"
ARTIFACTORY_CREDENTIALS = credentials('artifactory-credentials')
BUILD_CACHE_CREDENTIALS = credentials('gradle-ent-cache-credentials')
BUILD_CACHE_PASSWORD = "${env.BUILD_CACHE_CREDENTIALS_PSW}"
BUILD_CACHE_USERNAME = "${env.BUILD_CACHE_CREDENTIALS_USR}"
CORDA_ARTIFACTORY_PASSWORD = "${env.ARTIFACTORY_CREDENTIALS_PSW}"
CORDA_ARTIFACTORY_USERNAME = "${env.ARTIFACTORY_CREDENTIALS_USR}"
CORDA_GRADLE_SCAN_KEY = credentials('gradle-build-scans-key')
CORDA_BUILD_EDITION = "${buildEdition}"
CORDA_USE_CACHE = "corda-remotes"
DOCKER_URL = "https://index.docker.io/v1/"
EMAIL_RECIPIENTS = credentials('corda4-email-recipient')
INTEGRATION_ID = credentials('snyk-artifactory-c4')
SNYK_API_KEY = "c4-os-snyk" //Jenkins credential type: Snyk Api token
SNYK_TOKEN = credentials('c4-os-snyk-api-token-secret') //Jenkins credential type: Secret text
C4_OS_SNYK_ORG_ID = credentials('corda4-os-snyk-org-id')
Upgrade to gradle 7.6, kotlin 1.8 and jdk 17 Major changes due to JDK 17: 1. JDK17 JCE Provider now has built-in support for eddsas, corda uses the bouncycastle (i2p) implementation. This PR removes the conflicting algorithms from the built-in JCE provider. 2. JavaScript scripting has been removed from the JDK, the corda log4j config was using scripting to conditionally output additional diagnostic info if the MDC was populated. This PR has removed the scripting. 3. The artifactory plug-ins used are now deprecated, this PR has removed them and uses the same code as Corda 5 for publishing to artifactory. 4. Javadoc generation has been modified to use the latest dokka plug-ins. 5. Gradle 7.6 has implemented an incredibly annoying change where transitive dependencies are not put on the compile classpath, so that they have to be explicitly added as dependencies to projects. 6. Mockito has been updated, which sadly meant that quite a few source files have to changes to use the new (org.mockito.kotlin) package name. This makes this PR appear much larger than it is. 7. A number of tests have been marked as ignored to get a green, broadly they fall into 3 classes. The first is related to crypto keypair tests, it appears some logic in the JDK prefers to use the SunJCE implementation and we prefer to use bouncycastle. I believe this issue can be fixed with better test setup. The second group is related to our use of a method called "uncheckedCast(..)", the purpose of this method was to get rid of the annoying unchecked cast compiler warning that would otherwise exist. It looks like the Kotlin 1.9 compiler type inference differs and at runtime sometimes the type it infers is "Void" which causes an exception at runtime. The simplest solution is to use an explicit cast instead of unchecked cast, Corda 5 have removed unchecked cast from their codebase. The third class are a number of ActiveMQ tests which appear to have a memory leak somewhere.
2023-03-08 12:19:05 +00:00
JAVA_HOME = "/usr/lib/jvm/java-17-amazon-corretto"
ENT-11255: Scan attachments to determine if they are Kotlin 1.2 or later The node now sends a transaction to the verifier if any of its attachments were compiled with Kotlin 1.2 (the net.corda.node.verification.external system property has been removed). It uses kotlinx-metadata to read the Kotlin metadata in the attachment to determine this. For now this scanning is done each time the attachment is loaded from the database. The existing external verification integration tests were converted into smoke tests so that 4.11 nodes could be involved. This required various improvements to NodeProcess.Factory. A new JAVA_8_HOME environment variable, pointing to JDK 8, is required to run these tests. There is still some follow-up work that needs to be done: Sending transactions from a 4.11 node to a 4.12 node works, but not the other way round. A new WireTransaction component group needs to be introduced for storing 4.12 attachments so that they can be safely ignored by 4.11 nodes, and the 4.12 node needs to be able to load both 4.11 and 4.12 versions of the same contracts CorDapp so that they can be both attached to the transaction. Even though attachments are cached when retrieved from the database, the Kotlin metadata version should be stored in the attachments db table, rather than being scanned each time. Finally, VerificationService was refactored into NodeVerificationSupport and can be passed into SignedTransaction.verifyInternal, instead of needing the much heavier VerifyingServiceHub. This makes it easier for internal tools to verify transactions and spawn the verifier if necessary.
2024-01-22 11:31:51 +00:00
JAVA_8_HOME = "/usr/lib/jvm/java-1.8.0-amazon-corretto"
}
stages {
stage('Compile') {
steps {
authenticateGradleWrapper()
sh script: [
'./gradlew',
COMMON_GRADLE_PARAMS,
'clean',
'jar',
'--parallel'
].join(' ')
}
}
stage('Stash') {
when {
expression { params.DO_TEST }
}
steps {
stash name: 'compiled', useDefaultExcludes: false
}
}
stage('Snyk Security') {
when {
expression { isReleaseTag || isReleaseCandidate || isReleaseBranch }
}
steps {
script {
// Invoke Snyk for each Gradle sub project we wish to scan
def modulesToScan = ['node', 'capsule']
modulesToScan.each { module ->
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_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 {
2022-05-26 11:06:47 +00:00
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(' ')
}
}
}
}
}
}
2020-06-20 10:08:52 +00:00
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'
2020-06-20 10:08:52 +00:00
)
rtGradleRun(
usesPlugin: true,
useWrapper: true,
switches: '-s --info -DpublishApiDocs',
2020-06-20 10:08:52 +00:00
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 { isReleaseCandidate }
}
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}
}
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/open-source',
'--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
{
2020-06-20 10:08:52 +00:00
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")
2022-07-11 21:01:25 +00:00
if (isReleaseTag || isReleaseCandidate || isReleaseBranch) {
snykSecurityScan.generateHtmlElements()
}
if (isReleaseTag || isReleaseCandidate) {
// auto import and scanning of Docker images tag is dictated by below properties, so retrieve these first to scan the approproate tag
String cordaVersion = sh(script: 'grep "cordaVersion" constants.properties | awk -F= \'{print $2}\'', returnStdout: true).trim()
String versionSuffix = sh(script: 'grep "versionSuffix" constants.properties | awk -F= \'{print $2}\'', returnStdout: true).trim()
snykUtils.SnykApiImport(!versionSuffix.isEmpty() ? "${cordaVersion}-${versionSuffix}" : cordaVersion, SnykOrganisation.CORDA_4_OS, env.C4_OS_SNYK_ORG_ID)
}
}
}
unstable {
script {
sendSlackNotifications("warning", "BUILD UNSTABLE", false, "#corda-corda4-open-source-build-notifications")
if (isReleaseTag || isReleaseCandidate || isReleaseBranch) {
snykSecurityScan.generateHtmlElements()
}
2022-07-11 21:01:25 +00:00
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 */
}
}
2020-06-05 06:56:37 +00:00
}