#!groovy /** * Checkouts and builds notes for released components. * * Manuele Simi (ISTI-CNR) */ def agent_root_folder = '/var/lib/jenkins/.m2' // load the build report's content def text if (params.report) { println "Using local report" text = params.report } else { String reportURL = "https://code-repo.d4science.org/gCubeCI/gCubeReleases/raw/branch/master/closed/${gCube_release_version}/build_commits.${report_number}.csv" println "Pulling the report from Git at ${reportURL}" //load the report from the URL text = reportURL.toURL().getText() } // parse the report and extract the data def components = parseBuildCommits(text) assert 0 < components.size(): "No component found in build_commits.${report_number}.csv" for (component in components) { println "$component" } pipeline { agent { label 'CD' } environment { AGENT_ROOT_FOLDER = "${agent_root_folder}" GCUBE_RELEASE_NUMBER = "${params.gCube_release_version}" PIPELINE_BUILD_NUMBER = "${env.BUILD_NUMBER}" REPORT_NUMBER = "${params.report_number}" RELEASE_NOTES = "${agent_root_folder}/release_notes.${report_number}.md" } parameters { string(name: 'gCube_release_version', defaultValue: 'x.y.z', description: 'The number of the gCube release. Sample values: 4.14, 4.15, etc.') string(name: 'report_number', defaultValue: '', description: 'The build report number on Git to pull the notes.') string(name: 'report', defaultValue: '', description: 'The build report. Leave blank to pull the build report from Git.') booleanParam(name: 'use_report_commits', defaultValue: true, description: 'Use the commits on the report or the latest is on master?') } stages { stage('initialize reports') { steps { sh ''' date=`date` echo "# Release Notes for gCube ${gCube_release_version}" > $RELEASE_NOTES echo "" >> $RELEASE_NOTES echo "---" >> $RELEASE_NOTES ''' } } stage('notes') { steps { script { for (int i = 0; i < components.size(); i++) { stage(components[i]['name']) { appendHeading(components[i]['name'], components[i]['version'],components[i]['gitRepo'], components[i]['commitID']) createNotes(components[i]['name'], components[i]['gitRepo'], params.use_report_commits? components[i]['commitID']:null, components[i]['version'], gCube_release_version) } } appendFooter() } } } } // post-build actions post { always { script { sh ''' cp $RELEASE_NOTES ./release_notes.${PIPELINE_BUILD_NUMBER}.md cat ./release_notes.${PIPELINE_BUILD_NUMBER}.md ''' } } success { echo 'The release notes pipeline worked!' emailext to: 'jenkinsbuilds@d4science.org', subject: "[Jenkins build D4S] build ${currentBuild.fullDisplayName} worked", body: "Build time: ${currentBuild.durationString}. See ${env.BUILD_URL}" emailext attachmentsPattern: "**/release_notes.${PIPELINE_BUILD_NUMBER}.md", to: 'jenkinsreleases@d4science.org', subject: "Release notes for gCube ${GCUBE_RELEASE_NUMBER} (build #${PIPELINE_BUILD_NUMBER})", body: "Release notes extracted from the CHANGELOG.md(s) of the released components. See ${env.BUILD_URL}" } failure { echo 'The release notes pipeline has failed' emailext attachLog: true, to: 'jenkinsbuilds@d4science.org', subject: "[Jenkins build D4S] build ${currentBuild.fullDisplayName} failed", body: "Something is wrong with ${env.BUILD_URL}" } } } /** * Clones the repository, tags and creates notes * NOTE: 'credentialsId' be manually configured in Jenkins to access all the repos */ def createNotes(repo_name, repo_url, commit, version, gCube_release_version) { echo "Checkout SHA from reference $commit" sh(script: "rm -r ${repo_name} || true", returnStdout: true)?.trim() checkout([ $class : 'GitSCM', branches : [[name: commit? commit : '*/master']], doGenerateSubmoduleConfigurations: false, extensions : [ [$class: 'RelativeTargetDirectory', relativeTargetDir: repo_name], [$class: 'CloneOption', noTags: false, reference: ''] ], submoduleCfg : [], userRemoteConfigs : [ [credentialsId: '88b54962-1c0e-49cb-8155-22276860f346', url: repo_url] //git.gcube credentials on jenkins ] ]) def log_content = get_changelog(repo_name,repo_url) appendNotes(extract(repo_url,log_content, version, gCube_release_version,repo_name)) } String get_last_commit(repo_name) { String msg; dir(repo_name) { msg = sh(script: 'git rev-parse HEAD', returnStdout: true)?.trim() } return msg; } /** * Reads the CHANGELOG.md for the given repository. * * @param repo_name * @return the full content of CHANGELOG.md */ String get_changelog(repo_name,repo_url) { String text; dir(repo_name) { text = sh(script: 'cat CHANGELOG.md || echo Missing CHANGELOG.md', returnStdout: true)?.trim() } return text; } /** * Extracts the notes from the changelog's content. * * @param log_content * @param gCube_release_version * @param tag * @params component * @return the notes for then current release */ String extract(repo_url, log_content, tag, gCube_release_version, component) { println "Changelong content: ${log_content}" if (log_content.startsWith('Missing CHANGELOG.md')) return log_content + " at ${repo_url}" def section = '' def matcher = ("${log_content}" =~ /(?is)\[v$tag\](.*?)##\s+\[v.*?\]/) if (!matcher.find()) { def eof_matcher = ("${log_content}" =~ /(?is)\[v$tag\](.*$?)/) if (!eof_matcher.find()) { return "Tags ${tag} / ${gCube_release_version} not found in CHANGELOG.md at ${repo_url}" } else { assert eof_matcher[0][1]: "Missing release notes for ${component}" section = eof_matcher[0][1] } } else { assert matcher[0][1]: "Missing release notes for ${component}" section = matcher[0][1] } return section.replace("[r$gCube_release_version]", '') } /** * Appends the header to the release notes file. */ def appendHeader() { sh(""" echo "---" >> $RELEASE_NOTES """) } /** * Appends the footer to the release notes file. */ def appendFooter() { def now = new Date() sh(""" echo "" >> $RELEASE_NOTES echo "---" >> $RELEASE_NOTES echo "*generated by the gCube-ReleaseNotes pipeline from report ${REPORT_NUMBER}*" >> $RELEASE_NOTES echo "" >> $RELEASE_NOTES echo "*last update $now*" >> $RELEASE_NOTES """) } /** * Appends the new notes to the release notes file. * * @param repo_notes the notes for the repository to append */ def appendNotes(repo_notes) { sh(""" echo "${repo_notes}" >> $RELEASE_NOTES """) } /** * Appends heading to the release notes file. * * @param name the name of the repository * @param version the version of the component */ def appendHeading(name, version, gitRepo, commitID) { sh(""" echo "### ${name} ${version}" >> $RELEASE_NOTES echo "Tagged commit: _[${commitID}](${gitRepo}/commit/${commitID})_" >> $RELEASE_NOTES echo "" >> $RELEASE_NOTES """) } //a non CPS method is necessary for the usage of splitEachLine() @NonCPS def parseBuildCommits(def text) { def components = [] "${text}".splitEachLine(',') { columns -> if (columns[0].startsWith('#') || columns[0].startsWith('GroupID')) return components.add([ name : columns[1], version : columns[2], gitRepo : columns[3], commitID: columns[4] ] ) } return components }