import org.jenkinsci.plugins.pipeline.modeldefinition.Utils void call(Map options = [:]) { Map hooks = options.get('hooks', [ 'pre': null, 'pre-test': null, 'post-test': null, 'pre-build': null, 'post-build': null, 'pre-release': null, 'post-release': null, 'post-success': null, 'post-always': null, 'post-failure': null, ]) String testTask = options.get('testTask', 'test') String buildTask = options.get('buildTask', 'build') String releaseTask = options.get('releaseTask', 'release') String jobHistory = options.get('jobHistory', '5') String baseDockerfile = options.get('baseDockerfile', '') String baseImage = options.get('baseImage', 'reg.cadoles.com/proxy_cache/library/ubuntu:22.04') String dockerfileExtension = options.get('dockerfileExtension', '') List credentials = options.get('credentials', []) List releaseBranches = options.get('releaseBranches', ['develop', 'testing', 'stable', 'staging', 'master']) node { properties([ buildDiscarder(logRotator(daysToKeepStr: jobHistory, numToKeepStr: jobHistory)), ]) environment { // Set MKT_PROJECT_VERSION_BRANCH_NAME to Jenkins current branch name by default // See https://forge.cadoles.com/Cadoles/mktools project MKT_PROJECT_VERSION_BRANCH_NAME = env.BRANCH_NAME } stage('Cancel older jobs') { int buildNumber = env.BUILD_NUMBER as int if (buildNumber > 1) { milestone(buildNumber - 1) } milestone(buildNumber) } stage('Checkout project') { checkout(scm) } try { def containerImage = buildContainerImage(baseImage, baseDockerfile, dockerfileExtension) containerImage.inside('-v /var/run/docker.sock:/var/run/docker.sock --network host') { String repo = env.JOB_NAME if (env.BRANCH_NAME ==~ /^PR-.*$/) { repo = env.JOB_NAME - "/${env.JOB_BASE_NAME}" } List environment = prepareEnvironment() withEnv(environment) { withCredentials(credentials) { runHook(hooks, 'pre') stage('Build project') { runHook(hooks, 'pre-build') runTask('buildTask', buildTask) runHook(hooks, 'post-build') } stage('Run tests') { runHook(hooks, 'pre-test') catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { def ( status, output ) = runAndCaptureTask('testTask', testTask) if (!!output.trim() && env.CHANGE_ID) { String gitCommit = sh(script: 'git rev-parse --short HEAD', returnStdout: true) String report = """ |# Test report for ${gitCommit} | |
| |Output | |``` |${output} |``` | |
|""".trim().stripMargin() gitea.commentPullRequest(repo, env.CHANGE_ID, report) } if (status != 0) { throw new Exception("Task `${testTask}` failed !") } } runHook(hooks, 'post-test') } stage('Release project') { if (releaseBranches.contains(env.BRANCH_NAME)) { try { runHook(hooks, 'pre-release') runTask('releaseTask', releaseTask) runHook(hooks, 'post-release') } catch (Exception ex) { rocketSend( message: """ |:warning: Une erreur est survenue lors de la publication de [${repo}](https://forge.cadoles.com/${repo - env.JOB_BASE_NAME}): | | - **Commit:** [${env.GIT_COMMIT}](https://forge.cadoles.com/${repo - env.JOB_BASE_NAME}/commit/${env.GIT_COMMIT}) | - **Tags:** `${env.PROJECT_VERSION_TAG}` / `${env.PROJECT_VERSION_SHORT_TAG}` | | **Erreur** |``` |${ex} |``` | |[Visualiser le job](${env.RUN_DISPLAY_URL}) | |@${utils.getBuildUser()} """.stripMargin(), rawMessage: true ) throw ex } rocketSend( message: """ |:white_check_mark: Nouvelle publication terminée pour [${repo}](https://forge.cadoles.com/${repo - env.JOB_BASE_NAME}): | | - **Commit:** [${env.GIT_COMMIT}](https://forge.cadoles.com/${repo - env.JOB_BASE_NAME}/commit/${env.GIT_COMMIT}) | - **Tags:** `${env.PROJECT_VERSION_TAG}` / `${env.PROJECT_VERSION_SHORT_TAG}` | |[Visualiser le job](${env.RUN_DISPLAY_URL}) | |@${utils.getBuildUser()} """.stripMargin(), rawMessage: true ) } else { println("Current branch '${env.BRANCH_NAME}' not in releases branches (${releaseBranches}). Skipping.") Utils.markStageSkippedForConditional('Release project') } } } } } } catch (Exception ex) { runHook(hooks, 'post-failure', [ex]) throw ex } finally { runHook(hooks, 'post-always') cleanWs() } runHook(hooks, 'post-success') } } void buildContainerImage(String baseImage, String baseDockerfile, String dockerfileExtension) { String imageName = 'cadoles-standard-make-ci' dir(".${imageName}") { String dockerfile = '' if (baseDockerfile) { dockerfile = baseDockerfile } else { dockerfile = libraryResource 'com/cadoles/standard-make/Dockerfile' dockerfile = "FROM ${baseImage}\n" + dockerfile } dockerfile = """ ${dockerfile} ${dockerfileExtension} """ writeFile file:'Dockerfile', text: dockerfile String addLetsEncryptCA = libraryResource 'com/cadoles/common/add-letsencrypt-ca.sh' writeFile file:'add-letsencrypt-ca.sh', text:addLetsEncryptCA String safeJobName = URLDecoder.decode(env.JOB_NAME).toLowerCase().replace('/', '-').replace(' ', '-') String imageTag = "${safeJobName}-${env.BUILD_ID}" return docker.build("${imageName}:${imageTag}", '.') } } void runHook(Map hooks, String name, List args = []) { if (!hooks[name]) { println("No hook '${name}' defined. Skipping.") return } if (hooks[name] instanceof Closure) { hooks[name](*args) } else { error("Hook '${name}' seems to be defined but is not a closure !") } } void runTask(String name, task) { if (!task) { println("No task '${name}' defined. Skipping.") return [ -1, '' ] } sh(script: """#!/bin/bash make ${task} """) } List runAndCaptureTask(String name, task) { if (!task) { println("No task '${name}' defined. Skipping.") return [ -1, '' ] } String outputFile = ".${name}-output" int status = sh(script: """#!/bin/bash set -eo pipefail make ${task} 2>&1 | tee '${outputFile}' """, returnStatus: true) String output = readFile(outputFile) sh(script: "rm -f '${outputFile}'") return [status, output] } List prepareEnvironment() { List env = [] def ( longTag, shortTag ) = utils.getProjectVersionTags() env += ["PROJECT_VERSION_TAG=${longTag}"] env += ["PROJECT_VERSION_SHORT_TAG=${shortTag}"] String gitCommit = sh(script:'git rev-parse --short HEAD', returnStdout: true).trim() env += ["GIT_COMMIT=${gitCommit}"] Boolean isPR = utils.isPR() env += ["IS_PR=${isPR ? 'true' : 'false'}"] return env }