#!/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="" local patch="" # 检查是否包含小数点,如果有则提取minor和patch if echo "$clean_ver" | grep -q '\.'; then # 检查是否包含两个小数点(即有patch部分) if echo "$clean_ver" | grep -q '\..*\.'; then minor=$(echo "$clean_ver" | cut -d. -f2) patch=$(echo "$clean_ver" | cut -d. -f3) else # 只有一个小数点,即major.minor格式 minor=$(echo "$clean_ver" | cut -d. -f2) fi fi # 开始构建标签列表 local tags="$repo:v$clean_ver,$repo:$clean_ver" # 添加major标签(如果major与clean_ver不同) if [ -n "$major" ] && [ "$major" != "$clean_ver" ]; then tags="$tags,$repo:v$major,$repo:$major" fi # 添加major.minor标签(如果minor存在且组合不同于clean_ver) if [ -n "$minor" ]; then local major_minor="$major.$minor" if [ "$major_minor" != "$clean_ver" ]; then tags="$tags,$repo:v$major_minor,$repo:$major_minor" 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 . || { echo "==> 错误: 镜像构建失败" exit 1 } # ===== 分支触发 ===== 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"; then # 语义化分支 → 生成带 v 的多级标签; 支持纯数字版本号(如 12, v1, 1.2 等) 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 # 普通分支(如 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 . || { echo "==> 错误: 镜像构建失败" exit 1 } 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