From 75bda1960562ef653cef44bb7403141d9a54d4c6 Mon Sep 17 00:00:00 2001 From: bstein Date: Thu, 18 Dec 2025 01:26:53 -0300 Subject: [PATCH] ci: refactor harbor arm build pipeline --- Jenkinsfile | 222 ++++++++++++++++++++++++++-------------------------- README.md | 16 +++- 2 files changed, 125 insertions(+), 113 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7b35f9c..aa004e4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,6 +1,10 @@ pipeline { - triggers { - pollSCM('H/5 * * * *') + parameters { + string( + name: 'HARBOR_VERSION', + defaultValue: '', + description: 'Harbor tag to build (e.g. v2.14.1). Leave empty to build the latest release.' + ) } agent { kubernetes { @@ -12,52 +16,29 @@ kind: Pod spec: nodeSelector: kubernetes.io/arch: arm64 - hardware: rpi5 containers: - - name: dind - image: docker:27-dind + - name: builder + image: quay.io/podman/stable:v5.2 + tty: true + command: ["cat"] securityContext: privileged: true env: - - name: DOCKER_TLS_CERTDIR - value: "" - command: - - /bin/sh - - -c - args: - - | - set -euo pipefail - apk add --no-cache dnsmasq - cat > /etc/dnsmasq.conf <<'EOF' - filter-AAAA - # Use multiple upstream DNS servers to avoid transient resolver outages. - # Keep cluster DNS first for internal names, then fall back to public resolvers. - server=10.43.0.10 - server=1.1.1.1 - server=8.8.8.8 - EOF - dnsmasq - echo "nameserver 127.0.0.1" > /etc/resolv.conf - exec dockerd-entrypoint.sh --mtu=1400 - 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 + - name: REGISTRY_AUTH_FILE + value: /root/.docker/config.json + - name: XDG_RUNTIME_DIR + value: /var/run/containers volumeMounts: - name: workspace-volume mountPath: /home/jenkins/agent - - name: docker-config-writable - mountPath: /root/.docker - name: docker-config-secret mountPath: /docker-config + - name: containers-storage + mountPath: /var/lib/containers + - name: podman-runtime + mountPath: /var/run/containers volumes: - name: docker-config-secret secret: @@ -65,130 +46,147 @@ spec: items: - key: .dockerconfigjson path: config.json - - name: docker-config-writable - emptyDir: {} - name: workspace-volume emptyDir: {} - - name: dind-storage + - name: containers-storage + emptyDir: {} + - name: podman-runtime emptyDir: {} """ } } environment { - VERSION = 'v2.14.1' - TAG_SUFFIX = '-arm64' - // Avoid `REGISTRY` here because Harbor's Makefiles use `REGISTRY=registry` and - // run sub-makes with `-e`, so environment variables override make vars. IMAGE_NAMESPACE = 'registry.bstein.dev/infra' - HARBOR_TARBALL = "https://github.com/goharbor/harbor/archive/refs/tags/${VERSION}.tar.gz" + TAG_SUFFIX = '-arm64' + REGISTRY_URL = 'registry.bstein.dev' } options { disableConcurrentBuilds() + timestamps() } stages { - stage('Checkout Jenkinsfile') { + stage('Checkout pipeline repo') { steps { git credentialsId: 'gitea-pat', url: 'https://scm.bstein.dev/bstein/harbor-arm-build.git' } } - stage('Prep toolchain') { + stage('Setup tooling') { steps { container('builder') { sh ''' set -euo pipefail - apk add --no-cache bash curl make tar gzip git coreutils go ncurses + microdnf -y install git make curl tar gzip jq golang podman-docker python3 + mkdir -p "${DOCKER_CONFIG}" + cp /docker-config/config.json "${DOCKER_CONFIG}/config.json" + # Make the Docker CLI invoke podman (buildah backend). + ln -sf /usr/bin/podman /usr/local/bin/docker + mkdir -p "${XDG_RUNTIME_DIR}" ''' } } } - stage('Fetch harbor source') { + stage('Resolve version') { steps { container('builder') { - sh ''' + script { + def resolved = sh( + label: 'pick version', + returnStdout: true, + script: ''' + set -euo pipefail + if [ -n "${HARBOR_VERSION:-}" ]; then + echo "${HARBOR_VERSION}" + exit 0 + fi + curl -sSL https://api.github.com/repos/goharbor/harbor/releases/latest | python3 - <<'PY' +import json, sys +data = json.load(sys.stdin) +print(data.get("tag_name", "").strip()) +PY + ''' + ).trim() + env.HARBOR_VERSION_RESOLVED = resolved + env.HARBOR_SRC_DIR = "harbor-src/harbor-${resolved.startsWith('v') ? resolved.substring(1) : resolved}" + } + } + } + } + + stage('Fetch source') { + steps { + container('builder') { + sh """ set -euo pipefail rm -rf harbor-src mkdir -p harbor-src - curl -sSL "${HARBOR_TARBALL}" | tar xz -C harbor-src - ''' + curl -sSL "https://github.com/goharbor/harbor/archive/refs/tags/${env.HARBOR_VERSION_RESOLVED}.tar.gz" | tar xz -C harbor-src + ls -la harbor-src + """ } } } - stage('Build & push arm64 images') { + stage('Build & push (podman/buildah)') { steps { container('builder') { - sh ''' + sh """ set -euo pipefail - VERSIONTAG="${VERSION}${TAG_SUFFIX}.${BUILD_NUMBER}" - export VERSIONTAG - mkdir -p /root/.docker - cp /docker-config/config.json /root/.docker/config.json - - # Harbor's build uses git metadata; Jenkins workspace ownership can trigger - # Git's safe.directory protection in containerized builds. + export VERSIONTAG=\"${env.HARBOR_VERSION_RESOLVED}${TAG_SUFFIX}.${BUILD_NUMBER}\" + export BASEIMAGETAG=\"${env.HARBOR_VERSION_RESOLVED}${TAG_SUFFIX}.${BUILD_NUMBER}\" + export IMAGENAMESPACE=\"${IMAGE_NAMESPACE}\" + export BASEIMAGENAMESPACE=\"${IMAGE_NAMESPACE}\" + export DOCKERNETWORK=host + export PULL_BASE_FROM_DOCKERHUB=false + export BUILD_BASE=true + export BUILDTRIVYADP=false + export BUILD_INSTALLER=true + export BUILDAH_ISOLATION=chroot + export REGISTRY_AUTH_FILE=\"${DOCKER_CONFIG}/config.json\" git config --global --add safe.directory '*' + cd \"${env.HARBOR_SRC_DIR}\" - # `harbor-src/` itself matches `harbor-*`, so exclude it. - SRC_DIR=$(find harbor-src -mindepth 1 -maxdepth 1 -type d -name "harbor-*" | head -n1) - cd "${SRC_DIR}" - - export DOCKER_BUILDKIT=1 - - echo "Sanity-checking Makefile vars..." - make -n \ - VERSIONTAG="${VERSIONTAG}" \ - BASEIMAGETAG="${VERSIONTAG}" \ - IMAGENAMESPACE="${IMAGE_NAMESPACE}" \ - BASEIMAGENAMESPACE="${IMAGE_NAMESPACE}" \ - DOCKERNETWORK=host \ - PULL_BASE_FROM_DOCKERHUB=false \ - BUILD_BASE=true \ - BUILDTRIVYADP=false \ - BUILD_INSTALLER=true \ - build \ - | grep -E 'make -f .*/make/photon/Makefile build|IMAGENAMESPACE=|BASEIMAGENAMESPACE=|VERSIONTAG=|BASEIMAGETAG=|PULL_BASE_FROM_DOCKERHUB=' \ - || true - - # Harbor's root Makefile hard-sets many defaults (e.g. IMAGENAMESPACE=goharbor, - # PULL_BASE_FROM_DOCKERHUB=true). Environment exports are not sufficient because - # makefile assignments override the environment. Pass overrides on the make - # command line so they win. make compile - make \ - VERSIONTAG="${VERSIONTAG}" \ - BASEIMAGETAG="${VERSIONTAG}" \ - IMAGENAMESPACE="${IMAGE_NAMESPACE}" \ - BASEIMAGENAMESPACE="${IMAGE_NAMESPACE}" \ - DOCKERNETWORK=host \ - PULL_BASE_FROM_DOCKERHUB=false \ - BUILD_BASE=true \ - BUILDTRIVYADP=false \ - BUILD_INSTALLER=true \ + make \\ + VERSIONTAG=\"${env.HARBOR_VERSION_RESOLVED}${TAG_SUFFIX}.${BUILD_NUMBER}\" \\ + BASEIMAGETAG=\"${env.HARBOR_VERSION_RESOLVED}${TAG_SUFFIX}.${BUILD_NUMBER}\" \\ + IMAGENAMESPACE=\"${IMAGE_NAMESPACE}\" \\ + BASEIMAGENAMESPACE=\"${IMAGE_NAMESPACE}\" \\ + DOCKERNETWORK=host \\ + PULL_BASE_FROM_DOCKERHUB=false \\ + BUILD_BASE=true \\ + BUILDTRIVYADP=false \\ + BUILD_INSTALLER=true \\ build - # Retag a few upstream image names to our internal naming convention - # (so Helm values can keep using `harbor-*` consistently). - docker tag "${IMAGE_NAMESPACE}/prepare:${VERSIONTAG}" "${IMAGE_NAMESPACE}/harbor-prepare:${VERSIONTAG}" || true - docker tag "${IMAGE_NAMESPACE}/redis-photon:${VERSIONTAG}" "${IMAGE_NAMESPACE}/harbor-redis:${VERSIONTAG}" || true - docker tag "${IMAGE_NAMESPACE}/nginx-photon:${VERSIONTAG}" "${IMAGE_NAMESPACE}/harbor-nginx:${VERSIONTAG}" || true - docker tag "${IMAGE_NAMESPACE}/registry-photon:${VERSIONTAG}" "${IMAGE_NAMESPACE}/harbor-registry:${VERSIONTAG}" || true + for pair in \\ + \"prepare:harbor-prepare\" \\ + \"redis-photon:harbor-redis\" \\ + \"nginx-photon:harbor-nginx\" \\ + \"registry-photon:harbor-registry\"; do + src=\"${IMAGE_NAMESPACE}/$(echo "$pair" | cut -d: -f1):${env.HARBOR_VERSION_RESOLVED}${TAG_SUFFIX}.${BUILD_NUMBER}\" + dst=\"${IMAGE_NAMESPACE}/$(echo "$pair" | cut -d: -f2):${env.HARBOR_VERSION_RESOLVED}${TAG_SUFFIX}.${BUILD_NUMBER}\" + if podman image exists \"$src\"; then + podman tag \"$src\" \"$dst\" || true + fi + done - # Push every image we just built for this tag under our namespace. - docker images --format '{{.Repository}}:{{.Tag}}' \ - | awk -v ns="${IMAGE_NAMESPACE}/" -v tag="${VERSIONTAG}" 'index($0, ns)==1 && $0 ~ ":"tag"$"' \ - | sort -u \ + podman images --format '{{.Repository}}:{{.Tag}}' \\ + | awk -v ns=\"${IMAGE_NAMESPACE}/\" -v tag=\"${env.HARBOR_VERSION_RESOLVED}${TAG_SUFFIX}.${BUILD_NUMBER}\" 'index($0, ns)==1 && $0 ~ ":"tag"$"' \\ + | sort -u \\ | while read -r img; do - echo "Pushing ${img}" - docker push "${img}" + echo \"Pushing ${img}\" + podman push \"${img}\" done - ''' + """ } } } } post { - always { echo 'done' } + always { + echo "done" + } } } diff --git a/README.md b/README.md index 6c5e7f4..bd58a0b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,16 @@ # harbor-arm-build -Jenkins pipeline: build arm images and push to Harbor. + +Jenkins pipeline for building an arm64 Harbor release straight from upstream and +pushing the full image set into `registry.bstein.dev/infra`. + +Key behavior: +- Parameter `HARBOR_VERSION` (e.g. `v2.14.1`). If left empty, the pipeline + fetches the latest Harbor GitHub release tag. +- Runs on an arm64 node with podman/buildah (Docker CLI shim via `podman-docker`); + no dind is used. +- Builds all Harbor images with `IMAGENAMESPACE=registry.bstein.dev/infra` and + tags `${HARBOR_VERSION}-arm64.$BUILD_NUMBER`, then pushes them with the + `harbor-robot-pipeline` credentials. + +Trigger manually in Jenkins when a new Harbor version needs to be published to +the internal registry.