pipeline { agent { kubernetes { label 'metis' defaultContainer 'builder' yaml """ apiVersion: v1 kind: Pod metadata: labels: app: metis spec: nodeSelector: kubernetes.io/arch: arm64 node-role.kubernetes.io/worker: "true" imagePullSecrets: - name: harbor-robot-pipeline containers: - name: dind image: docker:27-dind securityContext: privileged: true env: - name: DOCKER_TLS_CERTDIR value: "" args: - --mtu=1400 - --host=unix:///var/run/docker.sock - --host=tcp://0.0.0.0:2375 - --tls=false volumeMounts: - name: dind-storage mountPath: /var/lib/docker - name: builder image: docker:27 command: ["cat"] tty: true env: - name: DOCKER_HOST value: tcp://localhost:2375 - name: DOCKER_TLS_CERTDIR value: "" - name: DOCKER_CONFIG value: /root/.docker volumeMounts: - name: workspace-volume mountPath: /home/jenkins/agent - name: docker-config-writable mountPath: /root/.docker - name: harbor-config mountPath: /docker-config - name: tester image: golang:1.23-bookworm command: ["cat"] tty: true volumeMounts: - name: workspace-volume mountPath: /home/jenkins/agent - name: publisher image: python:3.12-slim command: ["cat"] tty: true volumeMounts: - name: workspace-volume mountPath: /home/jenkins/agent volumes: - name: workspace-volume emptyDir: {} - name: docker-config-writable emptyDir: {} - name: dind-storage emptyDir: {} - name: harbor-config secret: secretName: harbor-robot-pipeline items: - key: .dockerconfigjson path: config.json """ } } environment { REGISTRY = 'registry.bstein.dev/bstein' IMAGE = "${REGISTRY}/metis" SENTINEL_IMAGE = "${REGISTRY}/metis-sentinel" VERSION_TAG = 'dev' SEMVER = 'dev' COVERAGE_JSON = 'build/coverage.json' JUNIT_XML = 'build/junit.xml' TEST_EXIT_CODE_PATH = 'build/test.exitcode' SUITE_NAME = 'metis' PUSHGATEWAY_URL = 'http://platform-quality-gateway.monitoring.svc.cluster.local:9091' } options { disableConcurrentBuilds() } triggers { pollSCM('H/5 * * * *') } stages { stage('Checkout') { steps { checkout scm } } stage('Unit tests') { steps { container('tester') { sh ''' set -eu apt-get update >/dev/null apt-get install -y --no-install-recommends xz-utils >/dev/null mkdir -p build go install github.com/jstemmer/go-junit-report/v2@latest set +e go test -v -coverprofile=build/coverage.out ./... > build/test.out 2>&1 test_rc=$? set -e printf '%s\n' "${test_rc}" > "${TEST_EXIT_CODE_PATH}" cat build/test.out "$(go env GOPATH)/bin/go-junit-report" < build/test.out > "${JUNIT_XML}" coverage="0" if [ -f build/coverage.out ]; then coverage="$(go tool cover -func=build/coverage.out | awk '/^total:/ {gsub("%","",$3); print $3}')" fi export GO_COVERAGE="${coverage}" printf '{"summary":{"percent_covered":%s}}\n' "${GO_COVERAGE}" > "${COVERAGE_JSON}" ''' } } } stage('Publish test metrics') { steps { container('publisher') { sh ''' set -eu python scripts/publish_test_metrics.py ''' } } } stage('Enforce test result') { steps { container('tester') { sh ''' set -eu test_rc="$(cat "${TEST_EXIT_CODE_PATH}")" exit "${test_rc}" ''' } } } stage('Prep toolchain') { steps { container('builder') { sh ''' set -eu mkdir -p /root/.docker cp /docker-config/config.json /root/.docker/config.json ''' } } } stage('Compute version') { steps { container('builder') { script { sh ''' set -eu SEMVER="0.1.0-${BUILD_NUMBER}" echo "SEMVER=${SEMVER}" > build.env ''' def props = readProperties file: 'build.env' env.SEMVER = props['SEMVER'] ?: "0.1.0-${env.BUILD_NUMBER}" env.VERSION_TAG = env.SEMVER } } } } stage('Buildx setup') { steps { container('builder') { sh ''' set -eu ready=0 for i in $(seq 1 90); do if docker info >/dev/null 2>&1; then ready=1 break fi sleep 2 done if [ "${ready}" -ne 1 ]; then echo "docker daemon did not become ready on ${DOCKER_HOST}" docker version || true exit 1 fi docker run --privileged --rm tonistiigi/binfmt --install amd64,arm64 BUILDER_NAME="metis-builder-${BUILD_NUMBER}" docker buildx rm "${BUILDER_NAME}" >/dev/null 2>&1 || true docker buildx create --name "${BUILDER_NAME}" --driver docker-container --use docker buildx inspect "${BUILDER_NAME}" --bootstrap ''' } } } stage('Build & push images') { steps { container('builder') { sh ''' set -eu VERSION_TAG="$(cut -d= -f2 build.env)" docker info >/dev/null for arch in amd64 arm64; do docker buildx build \ --platform "linux/${arch}" \ --build-arg TARGETOS=linux \ --build-arg TARGETARCH="${arch}" \ --tag "${IMAGE}:${VERSION_TAG}-${arch}" \ --tag "${IMAGE}:latest-${arch}" \ --target runtime \ --push \ . docker buildx build \ --platform "linux/${arch}" \ --build-arg TARGETOS=linux \ --build-arg TARGETARCH="${arch}" \ --tag "${SENTINEL_IMAGE}:${VERSION_TAG}-${arch}" \ --tag "${SENTINEL_IMAGE}:latest-${arch}" \ --target sentinel \ --push \ . done ''' } } } } post { always { script { if (fileExists('build/junit.xml')) { try { junit allowEmptyResults: true, testResults: 'build/junit.xml' } catch (Throwable err) { echo "junit step unavailable: ${err.class.simpleName}" } } } archiveArtifacts artifacts: 'build/junit.xml,build/coverage.json,build/coverage.out', allowEmptyArchive: true, fingerprint: true } } }