diff --git a/resources/com/cadoles/standard-make/Dockerfile b/resources/com/cadoles/standard-make/Dockerfile new file mode 100644 index 0000000..9b61535 --- /dev/null +++ b/resources/com/cadoles/standard-make/Dockerfile @@ -0,0 +1,26 @@ +ARG JQ_VERSION=1.6 + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + wget tar curl ca-certificates \ + openssl bash git unzip build-essential gnupg + +COPY add-letsencrypt-ca.sh /root/add-letsencrypt-ca.sh + +RUN bash /root/add-letsencrypt-ca.sh \ + && rm -f /root/add-letsencrypt-ca.sh + +# Install JQ +RUN wget -O /usr/local/bin/jq https://github.com/stedolan/jq/releases/download/jq-${JQ_VERSION}/jq-linux64 \ + && chmod +x /usr/local/bin/jq + +# Install Docker client +RUN install -m 0755 -d /etc/apt/keyrings \ + && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ + && chmod a+r /etc/apt/keyrings/docker.gpg \ + && echo \ + "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ + tee /etc/apt/sources.list.d/docker.list > /dev/null \ + && apt-get update \ + && apt-get install -y docker-ce-cli \ No newline at end of file diff --git a/vars/standardMakePipeline.groovy b/vars/standardMakePipeline.groovy new file mode 100644 index 0000000..b352093 --- /dev/null +++ b/vars/standardMakePipeline.groovy @@ -0,0 +1,155 @@ +import org.jenkinsci.plugins.pipeline.modeldefinition.Utils + +void call(Map options = [:]) { + Map hooks = options.get('hooks', [ + 'pre': 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', '10') + + String baseImage = options.get('baseImage', 'reg.cadoles.com/proxy_cache/library/ubuntu:22.04') + String baseDockerfile = options.get('baseDockerfile', '') + String dockerfileExtension = options.get('dockerfileExtension', '') + + List releaseBranches = options.get('releaseBranches', ['develop', 'testing', 'stable']) + + node { + properties([ + buildDiscarder(logRotator(daysToKeepStr: jobHistory, numToKeepStr: jobHistory)), + ]) + + stage('Cancel older jobs') { + int buildNumber = env.BUILD_NUMBER as int + if (buildNumber > 1) { + milestone(buildNumber - 1) + } + + milestone(buildNumber) + } + + stage('Checkout project') { + checkout(scm) + } + + stage('Run in container') { + try { + def containerImage = buildContainerImage(baseImage, baseDockerfile, dockerfileExtension) + containerImage.inside('-v /var/run/docker.sock:/var/run/docker.sock') { + String repo = env.JOB_NAME + if (env.BRANCH_NAME ==~ /^PR-.*$/) { + repo = env.JOB_NAME - "/${env.JOB_BASE_NAME}" + } + + stage('Run pre hooks') { + runHook(hooks, 'pre') + } + + stage('Run tests') { + catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { + def ( status, output ) = runTask('testTask', testTask) + + if (!!output.trim()) { + if (env.CHANGE_ID) { + gitea.commentPullRequest(repo, env.CHANGE_ID, "# Test report\n\n ${testReport}") + } else { + print testReport + } + } + + if (status != 0) { + throw new Exception('Task failed !') + } + } + } + + stage('Build project') { + def ( status ) = runTask('buildTask', buildTask) + if (status != 0) { + throw new Exception('Task failed !') + } + } + + stage('Release project') { + if (releaseBranches.contains(env.BRANCH_NAME)) { + def ( status ) = runTask('releaseTask', releaseTask) + if (status != 0) { + throw new Exception('Task failed !') + } + } 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 !") + } +} + +def runTask(String name, String task) { + if (!task) { + println("No task '${name}' defined. Skipping.") + return [0, ''] + } + + String tmpfile = sh(script: 'mktemp', returnStdout: true).trim() + int status = sh(script: "bash -eo pipefail -c \"( make ${task} 2>&1 ) | tee '${tmpfile}'\"", returnStatus: true) + String output = readFile(file: tmpfile) + + return [status, output] +}