214 lines
6.4 KiB
Bash
214 lines
6.4 KiB
Bash
#!/bin/sh
|
||
|
||
# 通用镜像编译和推送脚本
|
||
# 用于在 CI/CD 环境中构建和推送多架构 Docker 镜像到 SWR 注册表
|
||
|
||
set -e
|
||
|
||
# 脚本配置说明:
|
||
# - 需要设置 CI/CD 工具的 Secret 环境变量 SWR_REGISTRY, SWR_USERNAME, SWR_PASSWORD
|
||
# - 支持 CI_REPO_NAME, CI_COMMIT_TAG, CI_COMMIT_SHA, CI_COMMIT_BRANCH 等 CI/CD 变量
|
||
# - 默认构建 linux/amd64,linux/arm64 双架构镜像
|
||
# - main/master 分支推送时会额外推送 latest 标签
|
||
# - 符合 semver 规范形式的标签会生成多个标签 (vx.y.z -> vx.y, vx)
|
||
|
||
# 全局变量,记录生成的镜像及所有标签
|
||
IMAGES_TRACKED=""
|
||
|
||
# ========== 工具函数 ==========
|
||
|
||
# 删除字符串开头的 v
|
||
strip_v_prefix() {
|
||
case "$1" in
|
||
v*|V*) printf '%s\n' "${1#?}" ;;
|
||
*) printf '%s\n' "$1" ;;
|
||
esac
|
||
}
|
||
|
||
# 判断是否为 semver-like(支持 v?x 或 v?x.y 或 v?x.y.z)
|
||
is_semver_like() {
|
||
local input="$1"
|
||
# 支持: 123, v1, 2.3, V4.5.6 等,最多三级,必须全为数字
|
||
echo "$input" | grep -E '^[vV]?[0-9]+(\.[0-9]+){0,2}$' >/dev/null 2>&1
|
||
}
|
||
|
||
# 清洗 Docker 标签:替换非法字符(只保留 [a-zA-Z0-9_.-])
|
||
sanitize_label() {
|
||
local label="$1"
|
||
# 将 / \ { } ( ) [ ] 空格 等替换为 _
|
||
echo "$label" | sed 's/[^a-zA-Z0-9_.-]/_/g'
|
||
}
|
||
|
||
# 从 semver 字符串生成带 v 前缀的多级标签 (vx.y.z -> vx.y, vx)
|
||
generate_version_tags() {
|
||
local ver="$1"
|
||
local repo="$2"
|
||
|
||
local clean_ver=$(echo "$ver" | sed 's/^[vV]//')
|
||
|
||
# 提取各段
|
||
local major=$(echo "$clean_ver" | cut -d. -f1)
|
||
local minor=$(echo "$clean_ver" | cut -d. -f2)
|
||
local patch=$(echo "$clean_ver" | cut -d. -f3)
|
||
|
||
local tags="$repo:v$clean_ver"
|
||
|
||
# v<major>
|
||
if [ -n "$major" ] && [ "$major" != "$clean_ver" ]; then
|
||
tags="$tags,$repo:v$major"
|
||
fi
|
||
|
||
# v<major>.<minor>
|
||
if [ -n "$minor" ]; then
|
||
local mm="$major.$minor"
|
||
if [ "$mm" != "$clean_ver" ]; then
|
||
tags="$tags,$repo:v$mm"
|
||
fi
|
||
fi
|
||
|
||
echo "$tags"
|
||
}
|
||
|
||
# ========== CI 逻辑 ==========
|
||
|
||
# 验证必需的环境变量
|
||
validate_env() {
|
||
if [ -z "${SWR_REGISTRY:-}" ]; then
|
||
echo "==> 错误: SWR_REGISTRY 环境变量未设置"
|
||
exit 1
|
||
fi
|
||
|
||
if [ -z "${SWR_USERNAME:-}" ] || [ -z "${SWR_PASSWORD:-}" ]; then
|
||
echo "==> 错误: SWR_USERNAME 或 SWR_PASSWORD 未设置"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# 登录到 SWR 注册表
|
||
docker_login() {
|
||
echo "==> 登录到注册表: ${SWR_REGISTRY}"
|
||
# 使用管道避免密码在命令行中暴露
|
||
echo "${SWR_PASSWORD}" | docker login -u "${SWR_USERNAME}" --password-stdin "${SWR_REGISTRY}"
|
||
if [ $? -ne 0 ]; then
|
||
echo "==> 错误: 无法登录到注册表 ${SWR_REGISTRY}"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# 记录镜像标签到全局变量
|
||
track_image() {
|
||
local image="$1"
|
||
if [ -z "$IMAGES_TRACKED" ]; then
|
||
IMAGES_TRACKED="$image"
|
||
else
|
||
IMAGES_TRACKED="$IMAGES_TRACKED,$image"
|
||
fi
|
||
}
|
||
|
||
|
||
# 构建并推送镜像
|
||
build_and_push() {
|
||
local SWR_REPO="${SWR_REGISTRY_REPO:-${SWR_REGISTRY}/colovu/${CI_REPO_NAME}}"
|
||
local APP_VER_ARG=""
|
||
local build_args=""
|
||
|
||
# 设置 APP_VER(用于 Dockerfile ARG),始终去除 v/V
|
||
if [ -n "${CI_COMMIT_TAG:-}" ]; then
|
||
APP_VER_ARG="--build-arg APP_VER=$(strip_v_prefix "$CI_COMMIT_TAG")"
|
||
elif [ -n "${CI_COMMIT_BRANCH:-}" ] && [ "$CI_COMMIT_BRANCH" != "main" ] && [ "$CI_COMMIT_BRANCH" != "master" ]; then
|
||
APP_VER_ARG="--build-arg APP_VER=$(strip_v_prefix "$CI_COMMIT_BRANCH")"
|
||
fi
|
||
|
||
echo "==> Debug Variables"
|
||
echo "==> CI_REPO: ${CI_REPO:-empty}"
|
||
echo "==> CI_REPO_NAME: ${CI_REPO_NAME:-empty}"
|
||
echo "==> CI_COMMIT_TAG: ${CI_COMMIT_TAG:-empty}"
|
||
echo "==> CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH:-empty}"
|
||
echo "==> CI_COMMIT_SHA: ${CI_COMMIT_SHA:-empty}"
|
||
echo "==> SWR_REPO: $SWR_REPO"
|
||
echo "==> APP_VER_ARG: ${APP_VER_ARG:-empty}"
|
||
|
||
# ===== TAG 触发 =====
|
||
if [ -n "${CI_COMMIT_TAG:-}" ]; then
|
||
if is_semver_like "$CI_COMMIT_TAG"; then
|
||
TAGS=$(generate_version_tags "$CI_COMMIT_TAG" "$SWR_REPO")
|
||
else
|
||
# 非 semver tag → 清洗后使用
|
||
local safe_tag=$(sanitize_label "$CI_COMMIT_TAG")
|
||
TAGS="$SWR_REPO:$safe_tag"
|
||
fi
|
||
|
||
IFS=','
|
||
for tag in $TAGS; do
|
||
build_args="$build_args -t $tag"
|
||
track_image "$tag"
|
||
done
|
||
unset IFS
|
||
|
||
echo ""
|
||
echo "==> 构建 Tag 镜像: $TAGS"
|
||
docker buildx build --platform linux/amd64,linux/arm64 $build_args --progress=plain --push --provenance=false --sbom=false .
|
||
|
||
# ===== 分支触发 =====
|
||
elif [ -n "${CI_COMMIT_BRANCH:-}" ]; then
|
||
if [ "$CI_COMMIT_BRANCH" = "main" ] || [ "$CI_COMMIT_BRANCH" = "master" ]; then
|
||
# 主干分支
|
||
build_args="-t $SWR_REPO:latest"
|
||
track_image "$SWR_REPO:latest"
|
||
elif is_semver_like "$CI_COMMIT_BRANCH" && echo "$CI_COMMIT_BRANCH" | grep -q '\.'; then
|
||
# 语义化分支 → 生成带 v 的多级标签; 仅当包含 '.' 时才生成多级标签(如 v1.2.3 → v1.2, v1)
|
||
TAGS=$(generate_version_tags "$CI_COMMIT_BRANCH" "$SWR_REPO")
|
||
IFS=','
|
||
for tag in $TAGS; do
|
||
build_args="$build_args -t $tag"
|
||
track_image "$tag"
|
||
done
|
||
unset IFS
|
||
else
|
||
# 普通分支或纯数字(如 12, feature-x)→ 单标签 + sha
|
||
local safe_branch=$(sanitize_label "$CI_COMMIT_BRANCH")
|
||
build_args="-t $SWR_REPO:$safe_branch"
|
||
track_image "$SWR_REPO:$safe_branch"
|
||
fi
|
||
|
||
echo ""
|
||
echo "==> 构建 Branch 镜像: $build_args"
|
||
docker buildx build --platform linux/amd64,linux/arm64 $build_args --progress=plain --push --provenance=false --sbom=false .
|
||
|
||
else
|
||
echo "==> 错误: 无有效构建上下文"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
cleanup() {
|
||
echo ""
|
||
echo "==> 清理编译镜像及缓存..."
|
||
if [ -n "$IMAGES_TRACKED" ]; then
|
||
IFS=','
|
||
for img in $IMAGES_TRACKED; do
|
||
echo " - 清理: $img"
|
||
docker rmi "$img" 2>/dev/null || true
|
||
done
|
||
unset IFS
|
||
fi
|
||
|
||
echo " - 清理缓存"
|
||
docker buildx prune -f >/dev/null 2>&1 || true
|
||
docker rmi $(docker images -q -f "dangling=true") 2>/dev/null || true
|
||
}
|
||
|
||
# 主函数
|
||
main() {
|
||
validate_env
|
||
docker_login
|
||
build_and_push
|
||
cleanup
|
||
|
||
echo ""
|
||
echo "==> 编译镜像完成"
|
||
}
|
||
|
||
# 执行主函数
|
||
main
|