ci: build the dev images in parallel with the checks and gate only the push [CI]
This commit is contained in:
1 parent
b33e50696d
commit
1ccbb847b5
6 files changed
+99
-8
No files matched your search
@@ -15,10 +15,8 @@ when:
|
||||
branch: main
|
||||
evaluate: 'CI_COMMIT_MESSAGE contains "[CI]"'
|
||||
|
||||
# 先等后端测试和前端构建都通过再出镜像(提交信息写 [SKIP TESTS] 只跳过后端测试)
|
||||
depends_on:
|
||||
- verify
|
||||
- web-build
|
||||
# 镜像构建和 verify(后端快速测试)、web-build 并行跑,不再等它们:构建好之后、推送之前由 ci/wait-for-checks.sh 等这两个 workflow 都成功
|
||||
# (任何一个失败就不推;提交信息写 [SKIP TESTS] 只跳过后端测试,verify 照样是成功)。机器有 6 个 workflow 槽(WOODPECKER_MAX_WORKFLOWS),三个同时跑放得下。
|
||||
|
||||
concurrency: 1
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ if [[ "$component" == backend ]]; then
|
||||
ZOT_IMAGE_REPOSITORY="$repository" \
|
||||
IMAGE_TAGS="latest,$image_version" \
|
||||
IMAGE_PUBLISH=true \
|
||||
MOSHIRO_PUSH_GATE="$root/ci/wait-for-checks.sh" \
|
||||
run_bounded 30m bash "$root/ci/release-linux.sh" "$arch" backend
|
||||
exit 0
|
||||
fi
|
||||
@@ -150,6 +151,9 @@ run_bounded 30m buildah bud --layers --network host --tls-verify=true --ulimit n
|
||||
-f "$context/Containerfile" \
|
||||
-t "$local_image" "$context"
|
||||
|
||||
# 构建和测试是并行的:镜像已经好了,推送之前等 verify 和 web-build 都过(任何一个失败就不推)
|
||||
bash "$root/ci/wait-for-checks.sh"
|
||||
|
||||
logged_in=false
|
||||
for attempt in 1 2 3 4; do
|
||||
if run_bounded 2m buildah login --tls-verify=true --username "$ZOT_USERNAME" --password-stdin "$registry" <<< "$ZOT_SECRET" >/dev/null; then
|
||||
|
||||
+4
-2
@@ -45,8 +45,8 @@ export MOSHIRO_BUILT_AT="${MOSHIRO_BUILT_AT:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}"
|
||||
# 前端先构建:Backend.Api 在 web/*/dist 存在时把它们内嵌进程序(展示端 /,管理端 /admin/),这样 passkey 能绑定在面板自己的域名上
|
||||
# 发版流水线里有单独的 web-build 步骤先构建好了(并行的两个架构不能同时 npm ci);main 的 [CI] 构建只有一个 Backend 步骤,这里自己构建
|
||||
if [[ ! -f "$root/web/display/dist/index.html" || ! -f "$root/web/admin/dist/index.html" ]]; then
|
||||
if [[ -z "${NPM_CONFIG_CACHE:-}" && -d /home/justme/.npm ]]; then export NPM_CONFIG_CACHE=/home/justme/.npm; fi
|
||||
(cd "$root" && npm ci --prefix web --prefer-offline && npm run build --prefix web)
|
||||
# 这里的前端带着这次构建的版本号(apply_version 已经改了 package.json),不能用 web-build workflow 的产物;但依赖共用它的 node_modules 缓存(flock 防止同时读写),不再 npm ci
|
||||
WEB_SKIP_LINT=1 bash "$root/ci/web-build.sh"
|
||||
fi
|
||||
[[ -f "$root/web/display/dist/index.html" && -f "$root/web/admin/dist/index.html" ]] || { echo "the web build did not produce both apps" >&2; exit 1; }
|
||||
|
||||
@@ -110,6 +110,8 @@ if [[ "$image_publish" == false ]]; then
|
||||
fi
|
||||
export REGISTRY_AUTH_FILE="$root/.release/auth/zot-linux-$arch.json"
|
||||
mkdir -p "$(dirname "$REGISTRY_AUTH_FILE")"
|
||||
# 开发镜像的构建和测试是并行的:镜像已经好了,推送之前等 verify 和 web-build 都过(见 ci/wait-for-checks.sh;发版流水线不设这个)
|
||||
[[ -z "${MOSHIRO_PUSH_GATE:-}" ]] || bash "$MOSHIRO_PUSH_GATE"
|
||||
logged_in=false
|
||||
for attempt in 1 2 3 4; do
|
||||
if buildah login --tls-verify=true --username "$ZOT_USERNAME" --password-stdin "$registry" <<< "$ZOT_SECRET" >/dev/null; then
|
||||
|
||||
+16
-1
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
@@ -28,6 +29,20 @@ CONTAINERFILES = {
|
||||
}
|
||||
|
||||
|
||||
def copy_for_restore(path: Path, relative: Path, target: Path) -> None:
|
||||
"""The restore layer must not change when only the build version does: CI rewrites <Version> in the workspace
|
||||
Directory.Build.props (ci/version.sh), which used to invalidate the cached dependency restore on every commit.
|
||||
Restore only needs the committed props (package versions); the source copy still carries the real version."""
|
||||
if path.name == "Directory.Build.props":
|
||||
committed = subprocess.run(
|
||||
["git", "show", f"HEAD:backend/{relative.as_posix()}"], cwd=ROOT, capture_output=True, check=False
|
||||
)
|
||||
if committed.returncode == 0:
|
||||
target.write_bytes(committed.stdout)
|
||||
return
|
||||
shutil.copy2(path, target)
|
||||
|
||||
|
||||
def stage(component: str, runtime_identifier: str, destination: Path) -> None:
|
||||
if destination.exists():
|
||||
shutil.rmtree(destination)
|
||||
@@ -49,7 +64,7 @@ def stage(component: str, runtime_identifier: str, destination: Path) -> None:
|
||||
):
|
||||
target = restore / relative
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(path, target)
|
||||
copy_for_restore(path, relative, target)
|
||||
target = source / relative
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(path, target)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# 推镜像之前的闸门:等这个提交的 verify(后端快速测试)和 web-build 两个 workflow 都成功。
|
||||
# 镜像构建不再等它们(开发镜像的构建和测试并行跑,关键路径少了测试那一段);构建好之后、推送之前才来这里等结果,
|
||||
# 任何一个失败就不推(和以前「测试没过不出镜像」一样)。
|
||||
# 用法:wait-for-checks.sh (环境:CI_COMMIT_SHA、CI_REPO、CI_FORGE_URL,都是 Woodpecker 提供的)
|
||||
set -euo pipefail
|
||||
|
||||
sha="${CI_COMMIT_SHA:?CI_COMMIT_SHA is required}"
|
||||
repo="${CI_REPO:?CI_REPO is required}"
|
||||
forge="${CI_FORGE_URL:-https://git.oeo.one}"
|
||||
required=("ci/woodpecker/push/verify" "ci/woodpecker/push/web-build")
|
||||
# 总共最多等这么久(秒);一个 workflow 在这么久之后还没出现,说明它没有被触发,当作失败,免得一直挂着
|
||||
timeout_s="${MOSHIRO_CHECKS_TIMEOUT:-1200}"
|
||||
interval_s="${MOSHIRO_CHECKS_INTERVAL:-5}"
|
||||
auth=()
|
||||
[[ -n "${GITEA_TOKEN:-}" ]] && auth=(-H "Authorization: token $GITEA_TOKEN")
|
||||
|
||||
status_of() {
|
||||
# 每个 context 的最新状态;没有这个 context 输出 absent
|
||||
local body="$1" context="$2"
|
||||
python3 - "$context" "$body" <<'PY'
|
||||
import json, sys
|
||||
context, body = sys.argv[1], sys.argv[2]
|
||||
try:
|
||||
statuses = json.loads(body).get("statuses") or []
|
||||
except ValueError:
|
||||
print("unknown"); sys.exit(0)
|
||||
matches = [s for s in statuses if s.get("context") == context]
|
||||
print(matches[0].get("status", "unknown") if matches else "absent")
|
||||
PY
|
||||
}
|
||||
|
||||
deadline=$((SECONDS + timeout_s))
|
||||
last=""
|
||||
unreachable=0
|
||||
while :; do
|
||||
# 先直连(git.oeo.one 就在这台机器上,走代理回环反而可能不通),不行再用环境里的代理
|
||||
url="$forge/api/v1/repos/$repo/commits/$sha/status"
|
||||
body="$(curl -fsS --noproxy "*" --max-time 20 "${auth[@]}" "$url" 2>/dev/null || curl -fsS --max-time 20 "${auth[@]}" "$url" 2>/dev/null || true)"
|
||||
if [[ -z "$body" ]]; then
|
||||
unreachable=$((unreachable + 1))
|
||||
if ((unreachable >= 12)); then echo "could not read the commit status from $forge; not pushing the images" >&2; exit 1; fi
|
||||
sleep "$interval_s"
|
||||
continue
|
||||
fi
|
||||
unreachable=0
|
||||
summary=""
|
||||
pending=0
|
||||
for context in "${required[@]}"; do
|
||||
state="$(status_of "$body" "$context")"
|
||||
summary+="${context##*/}=$state "
|
||||
case "$state" in
|
||||
success) ;;
|
||||
failure|error|killed|declined)
|
||||
echo "check $context did not pass ($state); not pushing the images" >&2
|
||||
exit 1
|
||||
;;
|
||||
*) pending=1 ;;
|
||||
esac
|
||||
done
|
||||
[[ "$summary" == "$last" ]] || { echo "checks: $summary"; last="$summary"; }
|
||||
[[ "$pending" == 0 ]] && exit 0
|
||||
if ((SECONDS >= deadline)); then
|
||||
echo "checks did not finish within ${timeout_s}s ($summary); not pushing the images" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep "$interval_s"
|
||||
done
|
||||
+5
-1
@@ -9,6 +9,7 @@ export NPM_CONFIG_CACHE="${NPM_CONFIG_CACHE:-/home/justme/.npm}"
|
||||
web="$root/web"
|
||||
dirs=(node_modules display/node_modules admin/node_modules)
|
||||
mkdir -p "$cache"
|
||||
exec 9> "$cache/.lock"
|
||||
|
||||
key="$(cat "$web/package-lock.json" "$web/package.json" "$web/display/package.json" "$web/admin/package.json" | sha256sum | cut -c1-16)"
|
||||
|
||||
@@ -36,6 +37,8 @@ save() {
|
||||
(cd "$cache" && ls -1t | grep -v '\.tmp\.' | tail -n +4 | xargs -r rm -rf)
|
||||
}
|
||||
|
||||
# 并行的 workflow(web-build 和开发镜像里的前端构建)会同时读写这份缓存:恢复和保存期间持锁
|
||||
flock -w 600 9 || { echo "could not get the node_modules cache lock" >&2; exit 1; }
|
||||
if restore; then
|
||||
echo "restored node_modules from the cache ($key)"
|
||||
else
|
||||
@@ -44,6 +47,7 @@ else
|
||||
npm ci --prefix "$web" --prefer-offline
|
||||
save || echo "could not save the node_modules cache" >&2
|
||||
fi
|
||||
flock -u 9
|
||||
|
||||
# 展示端和管理端并行构建,再检查 CSS
|
||||
pids=()
|
||||
@@ -52,4 +56,4 @@ pids=()
|
||||
failed=0
|
||||
for pid in "${pids[@]}"; do wait "$pid" || failed=1; done
|
||||
[[ "$failed" == 0 ]] || { echo "web build failed" >&2; exit 1; }
|
||||
npm run lint:css --prefix "$web"
|
||||
[[ -n "${WEB_SKIP_LINT:-}" ]] || npm run lint:css --prefix "$web"
|
||||
Reference in new issue
Block a user