ci: add x64 backend docker pipeline
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
**
|
||||||
|
!Dockerfile.backend
|
||||||
|
!admin-web/**
|
||||||
|
!backend/**
|
||||||
|
|
||||||
|
admin-web/node_modules/
|
||||||
|
admin-web/dist/
|
||||||
|
admin-web/.env*
|
||||||
|
backend/**/bin/
|
||||||
|
backend/**/obj/
|
||||||
|
backend/**/TestResults/
|
||||||
|
backend/MiaoJiZhang.Api/wwwroot/assets/
|
||||||
|
backend/*.log
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# syntax=docker/dockerfile:1.7
|
||||||
|
|
||||||
|
FROM node:22-bookworm-slim AS admin-build
|
||||||
|
WORKDIR /src/admin-web
|
||||||
|
COPY admin-web/package.json admin-web/package-lock.json ./
|
||||||
|
RUN --mount=type=cache,target=/root/.npm \
|
||||||
|
npm ci --no-audit --no-fund
|
||||||
|
COPY admin-web/ ./
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim AS api-build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY backend/MiaoJiZhang.sln backend/
|
||||||
|
COPY backend/MiaoJiZhang.Api/MiaoJiZhang.Api.csproj backend/MiaoJiZhang.Api/
|
||||||
|
COPY backend/MiaoJiZhang.Domain/MiaoJiZhang.Domain.csproj backend/MiaoJiZhang.Domain/
|
||||||
|
COPY backend/MiaoJiZhang.Infrastructure/MiaoJiZhang.Infrastructure.csproj backend/MiaoJiZhang.Infrastructure/
|
||||||
|
RUN --mount=type=cache,target=/root/.nuget/packages \
|
||||||
|
dotnet restore backend/MiaoJiZhang.Api/MiaoJiZhang.Api.csproj
|
||||||
|
COPY backend/ backend/
|
||||||
|
ARG BUILD_VERSION=dev
|
||||||
|
RUN --mount=type=cache,target=/root/.nuget/packages \
|
||||||
|
dotnet publish backend/MiaoJiZhang.Api/MiaoJiZhang.Api.csproj \
|
||||||
|
--configuration Release \
|
||||||
|
--output /app/publish \
|
||||||
|
--no-restore \
|
||||||
|
--property:UseAppHost=false \
|
||||||
|
--property:InformationalVersion="$BUILD_VERSION" \
|
||||||
|
&& rm -rf /app/publish/wwwroot \
|
||||||
|
&& mkdir -p /app/publish/wwwroot
|
||||||
|
COPY --from=admin-build /src/admin-web/dist/ /app/publish/wwwroot/
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim AS runtime
|
||||||
|
ARG BUILD_VERSION=dev
|
||||||
|
ARG VCS_REF=unknown
|
||||||
|
ARG BUILD_DATE=unknown
|
||||||
|
LABEL org.opencontainers.image.title="JiZhi Backend" \
|
||||||
|
org.opencontainers.image.description="JiZhi admin web and ASP.NET backend" \
|
||||||
|
org.opencontainers.image.version="$BUILD_VERSION" \
|
||||||
|
org.opencontainers.image.revision="$VCS_REF" \
|
||||||
|
org.opencontainers.image.created="$BUILD_DATE" \
|
||||||
|
org.opencontainers.image.source="https://gitea.nxsir.cn/nanxun/jizhi"
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=api-build --chown=$APP_UID:$APP_UID /app/publish/ ./
|
||||||
|
ENV ASPNETCORE_HTTP_PORTS=8080 \
|
||||||
|
DOTNET_EnableDiagnostics=0
|
||||||
|
USER $APP_UID
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["dotnet", "MiaoJiZhang.Api.dll"]
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
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}"
|
||||||
|
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 version >/dev/null
|
||||||
|
docker 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 }')
|
||||||
|
test "$available_kb" -ge 8388608
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build linux-amd64 Image') {
|
||||||
|
steps {
|
||||||
|
retry(2) {
|
||||||
|
sh '''
|
||||||
|
set -euo pipefail
|
||||||
|
docker 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 image inspect "$IMAGE_REF" --format '{{.Os}}/{{.Architecture}}')" = 'linux/amd64'
|
||||||
|
test "$(docker image inspect "$IMAGE_REF" --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')" = "$FULL_SHA"
|
||||||
|
docker 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 network create "$SMOKE_NETWORK" >/dev/null
|
||||||
|
docker 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 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 logs "$SMOKE_MYSQL" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker run --detach \
|
||||||
|
--name "$SMOKE_APP" \
|
||||||
|
--network "$SMOKE_NETWORK" \
|
||||||
|
--publish 127.0.0.1::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
|
||||||
|
|
||||||
|
host_port=$(docker port "$SMOKE_APP" 8080/tcp | sed -n 's/.*://p' | head -n 1)
|
||||||
|
test -n "$host_port"
|
||||||
|
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 inspect "$SMOKE_APP" --format '{{.State.Running}}' | grep -qx true; then
|
||||||
|
docker logs "$SMOKE_APP" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
if [ "$app_ready" != 1 ]; then
|
||||||
|
docker 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 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 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()) {
|
||||||
|
sh(
|
||||||
|
script: '''
|
||||||
|
docker rm --force "$SMOKE_APP" "$SMOKE_MYSQL" >/dev/null 2>&1 || true
|
||||||
|
docker network rm "$SMOKE_NETWORK" >/dev/null 2>&1 || true
|
||||||
|
docker 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user