diff --git a/Jenkinsfile b/Jenkinsfile index af8c9b3..251bf9e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,73 +1,17 @@ +def isTimerBuild() { + return !currentBuild.getBuildCauses('hudson.triggers.TimerTrigger$TimerTriggerCause').isEmpty() +} + +def isScmBuild() { + return !currentBuild.getBuildCauses('hudson.triggers.SCMTrigger$SCMTriggerCause').isEmpty() +} + +def shouldPublishImage() { + return params.PUBLISH_IMAGE || (!isTimerBuild() && isScmBuild()) +} + pipeline { - agent { - kubernetes { - defaultContainer 'node' - yaml """ -apiVersion: v1 -kind: Pod -spec: - nodeSelector: - kubernetes.io/arch: arm64 - node-role.kubernetes.io/worker: "true" - containers: - - name: dind - image: docker:27-dind - securityContext: - privileged: true - env: - - name: DOCKER_TLS_CERTDIR - value: "" - command: - - /bin/sh - - -c - args: - - | - set -eu - exec dockerd-entrypoint.sh --mtu=1400 --host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:2375 - volumeMounts: - - name: dind-storage - mountPath: /var/lib/docker - - name: workspace-volume - mountPath: /home/jenkins/agent - - name: docker - image: docker:27 - command: ['cat'] - tty: true - env: - - name: DOCKER_HOST - value: tcp://localhost:2375 - - name: DOCKER_TLS_CERTDIR - value: "" - volumeMounts: - - name: docker-config-writable - mountPath: /root/.docker - - name: docker-config-secret - mountPath: /docker-config - - name: workspace-volume - mountPath: /home/jenkins/agent - - name: node - image: node:22-bookworm - command: ['cat'] - tty: true - volumeMounts: - - name: workspace-volume - mountPath: /home/jenkins/agent - volumes: - - name: docker-config-secret - secret: - secretName: harbor-robot-pipeline - items: - - key: .dockerconfigjson - path: config.json - - name: docker-config-writable - emptyDir: {} - - name: dind-storage - emptyDir: {} - - name: workspace-volume - emptyDir: {} -""" - } - } + agent none options { disableConcurrentBuilds() @@ -76,8 +20,8 @@ spec: parameters { booleanParam( name: 'PUBLISH_IMAGE', - defaultValue: true, - description: 'Build and push typhon image to Harbor on successful quality gate.' + defaultValue: false, + description: 'Build and push typhon image to Harbor; main branch SCM builds also publish automatically.' ) } @@ -88,28 +32,43 @@ spec: } stages { - stage('Checkout') { - steps { - checkout scm - } - } - - stage('Prep Docker Auth') { - steps { - container('docker') { - sh ''' - set -eu - mkdir -p /root/.docker - cp /docker-config/config.json /root/.docker/config.json - ''' + stage('Quality and metrics') { + agent { + kubernetes { + defaultContainer 'node' + yaml """ +apiVersion: v1 +kind: Pod +spec: + nodeSelector: + kubernetes.io/arch: arm64 + node-role.kubernetes.io/worker: "true" + containers: + - name: node + image: node:22-bookworm-slim + command: ['cat'] + tty: true + volumeMounts: + - name: workspace-volume + mountPath: /home/jenkins/agent + volumes: + - name: workspace-volume + emptyDir: {} +""" } } - } - stage('Run quality gate') { - steps { - container('node') { - sh ''' + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Run quality gate') { + steps { + container('node') { + sh ''' set -eu set +e bash <<'GATE' @@ -319,14 +278,14 @@ GATE JSON fi ''' + } + } } - } - } - stage('Publish test metrics') { - steps { - container('node') { - sh ''' + stage('Publish test metrics') { + steps { + container('node') { + sh ''' set -eu node <<'NODE' const fs = require('fs'); @@ -440,29 +399,103 @@ try { } NODE ''' + } + } } - } - } - stage('Enforce quality gate') { - steps { - container('node') { - sh ''' + stage('Enforce quality gate') { + steps { + container('node') { + sh ''' set -eu test "$(cat build/quality-gate.rc 2>/dev/null || echo 1)" -eq 0 ''' + } + } + } + } + + post { + always { + archiveArtifacts artifacts: 'build/**,dist/**,coverage/**', allowEmptyArchive: true, fingerprint: true } } } - stage('Buildx setup') { + stage('Build & push image') { when { - expression { return params.PUBLISH_IMAGE } + expression { return shouldPublishImage() } + } + agent { + kubernetes { + defaultContainer 'docker' + yaml """ +apiVersion: v1 +kind: Pod +spec: + nodeSelector: + kubernetes.io/arch: arm64 + node-role.kubernetes.io/worker: "true" + containers: + - name: dind + image: docker:27-dind + securityContext: + privileged: true + env: + - name: DOCKER_TLS_CERTDIR + value: "" + command: + - /bin/sh + - -c + args: + - | + set -eu + exec dockerd-entrypoint.sh --mtu=1400 --host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:2375 + volumeMounts: + - name: dind-storage + mountPath: /var/lib/docker + - name: workspace-volume + mountPath: /home/jenkins/agent + - name: docker + image: docker:27 + command: ['cat'] + tty: true + env: + - name: DOCKER_HOST + value: tcp://localhost:2375 + - name: DOCKER_TLS_CERTDIR + value: "" + volumeMounts: + - name: docker-config-writable + mountPath: /root/.docker + - name: docker-config-secret + mountPath: /docker-config + - name: workspace-volume + mountPath: /home/jenkins/agent + volumes: + - name: docker-config-secret + secret: + secretName: harbor-robot-pipeline + items: + - key: .dockerconfigjson + path: config.json + - name: docker-config-writable + emptyDir: {} + - name: dind-storage + emptyDir: {} + - name: workspace-volume + emptyDir: {} +""" + } } steps { + checkout scm container('docker') { sh ''' set -eu + mkdir -p /root/.docker + cp /docker-config/config.json /root/.docker/config.json + seq 1 20 | while read _; do docker info >/dev/null 2>&1 && break || sleep 2 done @@ -470,17 +503,17 @@ NODE BUILDER_NAME="typhon-${BUILD_NUMBER}" docker buildx rm "${BUILDER_NAME}" >/dev/null 2>&1 || true docker buildx create --name "${BUILDER_NAME}" --driver docker-container --bootstrap --use - ''' - } - } - } - stage('Build & push image') { - when { - expression { return params.PUBLISH_IMAGE } - } - steps { - container('docker') { + APP_VERSION="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p' package.json | head -n 1)" + SHORT_SHA="$(printf '%s' "${GIT_COMMIT:-nohash}" | cut -c1-8)" + IMAGE_TAG="${APP_VERSION}-${BUILD_NUMBER}-${SHORT_SHA}" + + { + echo "IMAGE_REPO=${IMAGE_REPO}" + echo "IMAGE_TAG=${IMAGE_TAG}" + echo "IMAGE_CHANNEL_TAG=main" + } > build/image.env + ''' withCredentials([ usernamePassword( credentialsId: 'harbor-robot', @@ -490,10 +523,7 @@ NODE ]) { sh ''' set -eu - . build/version.env - SHORT_SHA="$(printf '%s' "${GIT_COMMIT:-nohash}" | cut -c1-8)" - IMAGE_TAG="${APP_VERSION}-${BUILD_NUMBER}-${SHORT_SHA}" - + . build/image.env printf '%s' "${HARBOR_PASSWORD}" | docker login registry.bstein.dev -u "${HARBOR_USERNAME}" --password-stdin docker buildx build --platform linux/arm64 \ @@ -501,22 +531,10 @@ NODE --tag "${IMAGE_REPO}:${IMAGE_TAG}" \ --tag "${IMAGE_REPO}:main" \ --push . - - { - echo "IMAGE_REPO=${IMAGE_REPO}" - echo "IMAGE_TAG=${IMAGE_TAG}" - echo "IMAGE_CHANNEL_TAG=main" - } > build/image.env ''' } } } } } - - post { - always { - archiveArtifacts artifacts: 'build/**,dist/**,coverage/**', allowEmptyArchive: true, fingerprint: true - } - } }