206 lines
6.3 KiB
Bash
206 lines
6.3 KiB
Bash
#!/bin/sh
|
||
|
||
# 通用镜像编译和推送脚本
|
||
# 用于在 CI/CD 环境中构建和推送多架构 Docker 镜像到 SWR 注册表
|
||
|
||
set -ex
|
||
|
||
# 脚本配置说明:
|
||
# - 需要设置 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=""
|
||
|
||
# ========== 工具函数 ==========
|
||
|
||
# 判断是否为 semver-like(支持 v?x.y 或 v?x.y.z)
|
||
is_semver_like() {
|
||
local input="$1"
|
||
echo "$input" | grep -E '^[vV]?[0-9]+\.[0-9]+(\.[0-9]+)?$' >/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"
|
||
|
||
# 提取纯数字版本(去掉 v/V)
|
||
local clean_ver=$(echo "$ver" | sed 's/^[vV]//')
|
||
|
||
# 拆分为数组
|
||
IFS='.' read -r major minor patch <<EOF
|
||
$(echo "$clean_ver" | awk -F. '{print $1"."$2"."$3}')
|
||
EOF
|
||
|
||
# 始终以 v 开头构建标签
|
||
local tags="$repo:v$clean_ver"
|
||
|
||
# v<major>
|
||
if [ -n "$major" ]; then
|
||
tags="$tags,$repo:v$major"
|
||
fi
|
||
|
||
# v<major>.<minor>(仅当存在 minor)
|
||
if [ -n "$minor" ]; then
|
||
tags="$tags,$repo:v$major.$minor"
|
||
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=""
|
||
|
||
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}"
|
||
echo "========================"
|
||
|
||
# 设置 APP_VER(用于 Dockerfile ARG),始终去除 v/V
|
||
if [ -n "${CI_COMMIT_TAG:-}" ]; then
|
||
APP_VER_ARG="--build-arg APP_VER=$(echo "$CI_COMMIT_TAG" | sed 's/^[vV]//')"
|
||
elif [ -n "${CI_COMMIT_BRANCH:-}" ] && [ "$CI_COMMIT_BRANCH" != "main" ] && [ "$CI_COMMIT_BRANCH" != "master" ]; then
|
||
APP_VER_ARG="--build-arg APP_VER=$(echo "$CI_COMMIT_BRANCH" | sed 's/^[vV]//')"
|
||
fi
|
||
|
||
# ===== 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 $APP_VER_ARG $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
|
||
# 主干分支
|
||
local short_sha=$(echo "$CI_COMMIT_SHA" | cut -c1-8)
|
||
build_args="-t $SWR_REPO:latest -t $SWR_REPO:$short_sha"
|
||
track_image "$SWR_REPO:latest"
|
||
track_image "$SWR_REPO:$short_sha"
|
||
elif is_semver_like "$CI_COMMIT_BRANCH"; then
|
||
# 语义化分支 → 生成带 v 的多级标签
|
||
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
|
||
# 普通分支 → 清洗后使用
|
||
local safe_branch=$(sanitize_label "$CI_COMMIT_BRANCH")
|
||
local short_sha=$(echo "$CI_COMMIT_SHA" | cut -c1-8)
|
||
build_args="-t $SWR_REPO:$safe_branch -t $SWR_REPO:$short_sha"
|
||
track_image "$SWR_REPO:$safe_branch"
|
||
track_image "$SWR_REPO:$short_sha"
|
||
fi
|
||
|
||
echo ""
|
||
echo "构建 Branch 镜像: $(echo "$build_args" | sed 's/-t /\n/t /g' | tail -n +2 | tr '\n' ' ')"
|
||
docker buildx build --platform linux/amd64,linux/arm64 $APP_VER_ARG $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 " - $image"
|
||
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
|
||
}
|
||
|
||
# 执行主函数
|
||
main
|