#!/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 < if [ -n "$major" ]; then tags="$tags,$repo:v$major" fi # v.(仅当存在 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