ci: refactor harbor arm build pipeline
This commit is contained in:
parent
8f3b22eb58
commit
75bda19605
222
Jenkinsfile
vendored
222
Jenkinsfile
vendored
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
README.md
16
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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user