263 lines
11 KiB
Plaintext
263 lines
11 KiB
Plaintext
pipeline {
|
|
agent {
|
|
node {
|
|
label '构建机1'
|
|
customWorkspace '/home/nanxunai/goujian/workspace/jizhang-backend-docker'
|
|
}
|
|
}
|
|
|
|
options {
|
|
timestamps()
|
|
disableConcurrentBuilds()
|
|
skipDefaultCheckout(true)
|
|
timeout(time: 120, unit: 'MINUTES')
|
|
buildDiscarder(logRotator(daysToKeepStr: '30', numToKeepStr: '20'))
|
|
}
|
|
|
|
environment {
|
|
PATH = '/usr/local/bin:/usr/bin:/bin'
|
|
HTTP_PROXY = 'http://192.168.5.200:7890'
|
|
HTTPS_PROXY = 'http://192.168.5.200:7890'
|
|
http_proxy = 'http://192.168.5.200:7890'
|
|
https_proxy = 'http://192.168.5.200:7890'
|
|
NO_PROXY = '127.0.0.1,localhost,192.168.5.8,192.168.5.100,.nxsir.cn'
|
|
no_proxy = '127.0.0.1,localhost,192.168.5.8,192.168.5.100,.nxsir.cn'
|
|
OPENLIST_CREDENTIALS = 'openlist_key'
|
|
OPENLIST_BASE_URL = 'https://openlist.nxsir.cn'
|
|
OPENLIST_REMOTE_DIR = '/yidongpan/构建产物/jizhang/backend-docker/linux-amd64'
|
|
}
|
|
|
|
stages {
|
|
stage('Checkout') {
|
|
steps {
|
|
deleteDir()
|
|
checkout scm
|
|
}
|
|
}
|
|
|
|
stage('Metadata And Preflight') {
|
|
steps {
|
|
script {
|
|
env.SHORT_SHA = sh(script: 'git rev-parse --short=8 HEAD', returnStdout: true).trim()
|
|
env.FULL_SHA = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
|
|
env.BUILD_DATE = sh(script: 'date -u +%Y-%m-%dT%H:%M:%SZ', returnStdout: true).trim()
|
|
env.BACKEND_VERSION = sh(script: 'date -u +%Y%m%d-%H%M', returnStdout: true).trim()
|
|
env.IMMUTABLE_TAG = "${env.BACKEND_VERSION}-b${env.BUILD_NUMBER}-${env.SHORT_SHA}"
|
|
env.IMAGE_REF = "jizhi-backend:${env.IMMUTABLE_TAG}"
|
|
env.ARCHIVE_BASENAME = "JiZhi-Backend-${env.IMMUTABLE_TAG}-linux-amd64.tar.gz"
|
|
env.ARCHIVE_PATH = "${env.WORKSPACE}/artifacts/${env.ARCHIVE_BASENAME}"
|
|
env.MANIFEST_PATH = "${env.WORKSPACE}/artifacts/JiZhi-Backend-${env.IMMUTABLE_TAG}-manifest.json"
|
|
env.SMOKE_NETWORK = "jizhi-smoke-net-${env.BUILD_NUMBER}"
|
|
env.SMOKE_MYSQL = "jizhi-smoke-mysql-${env.BUILD_NUMBER}"
|
|
env.SMOKE_APP = "jizhi-smoke-app-${env.BUILD_NUMBER}"
|
|
env.DOCKER_CMD = sh(
|
|
script: '''
|
|
set +x
|
|
if docker version >/dev/null 2>&1; then
|
|
printf 'docker'
|
|
elif sudo -n docker version >/dev/null 2>&1; then
|
|
printf 'sudo -n docker'
|
|
else
|
|
echo 'Jenkins 用户无法访问 Docker;请加入 docker 组或配置免密 sudo docker。' >&2
|
|
exit 1
|
|
fi
|
|
''',
|
|
returnStdout: true,
|
|
).trim()
|
|
currentBuild.displayName = "#${env.BUILD_NUMBER} ${env.IMMUTABLE_TAG}"
|
|
currentBuild.description = 'linux/amd64 · Admin Web + ASP.NET API'
|
|
}
|
|
sh '''
|
|
set -euo pipefail
|
|
test "$(uname -m)" = x86_64
|
|
for command_name in git docker curl gzip sha256sum python3; do
|
|
command -v "$command_name" >/dev/null
|
|
done
|
|
$DOCKER_CMD version >/dev/null
|
|
$DOCKER_CMD buildx version
|
|
test -f Dockerfile.backend
|
|
test -f admin-web/package-lock.json
|
|
mkdir -p artifacts
|
|
available_kb=$(df -Pk "$WORKSPACE" | awk 'NR == 2 { print $4 }')
|
|
# Warm Docker layers consume disk but make incremental builds
|
|
# much smaller; keep 4 GiB free for image export and smoke tests.
|
|
test "$available_kb" -ge 4194304
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Build linux-amd64 Image') {
|
|
steps {
|
|
retry(2) {
|
|
sh '''
|
|
set -euo pipefail
|
|
$DOCKER_CMD buildx build \
|
|
--platform linux/amd64 \
|
|
--load \
|
|
--file Dockerfile.backend \
|
|
--build-arg "BUILD_VERSION=$IMMUTABLE_TAG" \
|
|
--build-arg "VCS_REF=$FULL_SHA" \
|
|
--build-arg "BUILD_DATE=$BUILD_DATE" \
|
|
--tag "$IMAGE_REF" \
|
|
.
|
|
'''
|
|
}
|
|
sh '''
|
|
set -euo pipefail
|
|
test "$($DOCKER_CMD image inspect "$IMAGE_REF" --format '{{.Os}}/{{.Architecture}}')" = 'linux/amd64'
|
|
test "$($DOCKER_CMD image inspect "$IMAGE_REF" --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')" = "$FULL_SHA"
|
|
$DOCKER_CMD run --rm --entrypoint /bin/sh "$IMAGE_REF" -c \
|
|
'test -s /app/MiaoJiZhang.Api.dll && test -s /app/wwwroot/index.html'
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Smoke Test Image') {
|
|
steps {
|
|
sh '''
|
|
set -euo pipefail
|
|
$DOCKER_CMD network create "$SMOKE_NETWORK" >/dev/null
|
|
$DOCKER_CMD run --detach \
|
|
--name "$SMOKE_MYSQL" \
|
|
--network "$SMOKE_NETWORK" \
|
|
--env MYSQL_DATABASE=jizhi_smoke \
|
|
--env MYSQL_USER=jizhi \
|
|
--env MYSQL_PASSWORD=jizhi-smoke-password \
|
|
--env MYSQL_ROOT_PASSWORD=jizhi-smoke-root-password \
|
|
mysql:8.4 >/dev/null
|
|
|
|
mysql_ready=0
|
|
for attempt in $(seq 1 60); do
|
|
if $DOCKER_CMD exec "$SMOKE_MYSQL" mysqladmin ping \
|
|
--user=jizhi --password=jizhi-smoke-password --silent; then
|
|
mysql_ready=1
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
if [ "$mysql_ready" != 1 ]; then
|
|
$DOCKER_CMD logs "$SMOKE_MYSQL" >&2
|
|
exit 1
|
|
fi
|
|
|
|
host_port=$((18080 + BUILD_NUMBER % 1000))
|
|
$DOCKER_CMD run --detach \
|
|
--name "$SMOKE_APP" \
|
|
--network "$SMOKE_NETWORK" \
|
|
--publish "127.0.0.1:${host_port}:8080" \
|
|
--env ASPNETCORE_ENVIRONMENT=Production \
|
|
--env "ConnectionStrings__Default=Server=$SMOKE_MYSQL;Port=3306;Database=jizhi_smoke;User=jizhi;Password=jizhi-smoke-password" \
|
|
--env Jwt__Secret=jizhi-smoke-jwt-secret-at-least-32-characters \
|
|
--env Jwt__Issuer=MiaoJiZhang.Api \
|
|
--env Jwt__Audience=MiaoJiZhang.App \
|
|
--env Admin__BootstrapUsername=smoke_admin \
|
|
--env Admin__BootstrapPassword=jizhi-smoke-admin-password \
|
|
--env Admin__CookieSecure=false \
|
|
--env "Build__Version=$IMMUTABLE_TAG" \
|
|
"$IMAGE_REF" >/dev/null
|
|
|
|
app_ready=0
|
|
for attempt in $(seq 1 90); do
|
|
if curl --fail --silent --show-error \
|
|
"http://127.0.0.1:$host_port/api/ping" >artifacts/smoke-ping.json; then
|
|
app_ready=1
|
|
break
|
|
fi
|
|
if ! $DOCKER_CMD inspect "$SMOKE_APP" --format '{{.State.Running}}' | grep -qx true; then
|
|
$DOCKER_CMD logs "$SMOKE_APP" >&2
|
|
exit 1
|
|
fi
|
|
sleep 2
|
|
done
|
|
if [ "$app_ready" != 1 ]; then
|
|
$DOCKER_CMD logs "$SMOKE_APP" >&2
|
|
exit 1
|
|
fi
|
|
grep -Fq "\"version\":\"$IMMUTABLE_TAG\"" artifacts/smoke-ping.json
|
|
curl --fail --silent --show-error "http://127.0.0.1:$host_port/" \
|
|
| grep -Fq '<div id="app"></div>'
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Package Image') {
|
|
steps {
|
|
sh '''
|
|
set -euo pipefail
|
|
$DOCKER_CMD save "$IMAGE_REF" | gzip -1 >"$ARCHIVE_PATH"
|
|
gzip -t "$ARCHIVE_PATH"
|
|
gzip -dc "$ARCHIVE_PATH" | tar -tf - | grep -q '^manifest.json$'
|
|
sha256sum "$ARCHIVE_PATH" >"$ARCHIVE_PATH.sha256"
|
|
image_id=$($DOCKER_CMD image inspect "$IMAGE_REF" --format '{{.Id}}')
|
|
archive_sha=$(awk '{print $1}' "$ARCHIVE_PATH.sha256")
|
|
export image_id archive_sha
|
|
python3 - <<'PY'
|
|
import json, os, pathlib
|
|
path = pathlib.Path(os.environ['MANIFEST_PATH'])
|
|
payload = {
|
|
'project': 'jizhang-backend',
|
|
'buildNumber': os.environ['BUILD_NUMBER'],
|
|
'version': os.environ['IMMUTABLE_TAG'],
|
|
'commit': os.environ['FULL_SHA'],
|
|
'platform': 'linux/amd64',
|
|
'image': os.environ['IMAGE_REF'],
|
|
'imageId': os.environ['image_id'],
|
|
'archive': pathlib.Path(os.environ['ARCHIVE_PATH']).name,
|
|
'archiveSha256': os.environ['archive_sha'],
|
|
'builtAt': os.environ['BUILD_DATE'],
|
|
'contents': ['admin-web', 'MiaoJiZhang.Api'],
|
|
'smokeTests': ['image architecture', '/api/ping', 'admin web index'],
|
|
}
|
|
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
|
|
PY
|
|
sha256sum "$MANIFEST_PATH" >"$MANIFEST_PATH.sha256"
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Upload To OpenList') {
|
|
steps {
|
|
withCredentials([usernamePassword(credentialsId: "${OPENLIST_CREDENTIALS}", usernameVariable: 'OPENLIST_USERNAME', passwordVariable: 'OPENLIST_PASSWORD')]) {
|
|
sh '''
|
|
set -euo pipefail
|
|
for artifact in \
|
|
"$ARCHIVE_PATH" \
|
|
"$ARCHIVE_PATH.sha256" \
|
|
"$MANIFEST_PATH" \
|
|
"$MANIFEST_PATH.sha256"; do
|
|
./scripts/upload-openlist-artifact.sh "$artifact" "$OPENLIST_REMOTE_DIR"
|
|
done
|
|
'''
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
post {
|
|
always {
|
|
script {
|
|
if (env.NODE_NAME?.trim() && env.DOCKER_CMD?.trim()) {
|
|
sh(
|
|
script: '''
|
|
$DOCKER_CMD rm --force "$SMOKE_APP" "$SMOKE_MYSQL" >/dev/null 2>&1 || true
|
|
$DOCKER_CMD network rm "$SMOKE_NETWORK" >/dev/null 2>&1 || true
|
|
$DOCKER_CMD image rm "$IMAGE_REF" >/dev/null 2>&1 || true
|
|
''',
|
|
returnStatus: true,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
success {
|
|
archiveArtifacts artifacts: 'artifacts/*.json,artifacts/*.sha256', fingerprint: true
|
|
}
|
|
cleanup {
|
|
script {
|
|
if (env.NODE_NAME?.trim()) {
|
|
cleanWs(deleteDirs: true, notFailBuild: true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|