name: Build Any Repository Docker Image on: workflow_dispatch: inputs: repo_url: description: '要构建的仓库地址(支持相对路径如:xc-workflows/engine-demo,或完整 URL 如:https://dev.modelhub.org.cn/xc-workflows/engine-demo)' required: true type: string ref: description: '分支/tag/commit' required: true default: 'main' type: string dry_run: description: '仅构建不推送' required: false default: 'true' type: choice options: - 'true' - 'false' job_id: description: '任务ID' required: true type: string submission_id: description: '提交ID' required: true type: string callback_url: description: '回调地址' required: false default: '' type: string callback_token: description: '回调Token' required: false default: '' type: string startup_params: description: '启动参数(JSON格式)' required: false default: '{}' type: string jobs: build: runs-on: amd64-ubuntu-24.04 steps: - name: Debug URL info run: | echo "Server URL: ${{ gitea.server_url }}" echo "Repo URL input: ${{ github.event.inputs.repo_url }}" echo "Ref: ${{ github.event.inputs.ref }}" echo "Dry run: ${{ github.event.inputs.dry_run }}" echo "Job ID: ${{ github.event.inputs.job_id }}" echo "Submission ID: ${{ github.event.inputs.submission_id }}" echo "Callback URL: ${{ github.event.inputs.callback_url }}" echo "Startup Params: ${{ github.event.inputs.startup_params }}" - name: Clone target repository run: | REPO_URL="${{ github.event.inputs.repo_url }}" # 判断是否完整 URL if [[ "$REPO_URL" == http://* || "$REPO_URL" == https://* ]]; then CLONE_URL="$REPO_URL" # 如果没有 .git 后缀,加上 [[ "$CLONE_URL" != *.git ]] && CLONE_URL="${CLONE_URL}.git" # 提取路径部分用于生成镜像名 REPO_PATH="$(echo "$REPO_URL" | sed 's|https\?://[^/]*/||' | sed 's|\.git$||')" else # 拼接完整的 clone URL(gitea.server_url 已包含协议) CLONE_URL="${{ gitea.server_url }}/$REPO_URL.git" REPO_PATH="$REPO_URL" fi echo "正在克隆: $CLONE_URL" git clone "$CLONE_URL" target_repo cd target_repo git checkout "${{ github.event.inputs.ref }}" # 打 tag TAG_NAME="build-${{ github.event.inputs.submission_id }}-${{ github.event.inputs.job_id }}" git tag "$TAG_NAME" echo "已打 tag: $TAG_NAME" # 保存仓库信息供后续步骤使用 echo "TARGET_REPO_PATH=$(pwd)" >> "$GITEA_ENV" echo "TARGET_REPO_NAME=$(basename "$REPO_PATH")" >> "$GITEA_ENV" echo "REPO_PATH=$REPO_PATH" >> "$GITEA_ENV" echo "TAG_NAME=$TAG_NAME" >> "$GITEA_ENV" echo "克隆完成,当前目录: $(pwd)" - name: Set image metadata run: | cd target_repo # 生成镜像名称(将仓库路径中的 / 替换为 -) IMAGE_NAME="$(echo "$REPO_PATH" | tr '[:upper:]' '[:lower:]' | tr '/' '-')" SAFE_TAG="${TAG_NAME}" IMAGE="${DOCKER_REGISTRY}/${DOCKER_USERNAME}/${IMAGE_NAME}:${SAFE_TAG}" echo "IMAGE_NAME=${IMAGE_NAME}" >> "$GITEA_ENV" echo "IMAGE=${IMAGE}" >> "$GITEA_ENV" echo "SAFE_TAG=${SAFE_TAG}" >> "$GITEA_ENV" echo "镜像名称: ${IMAGE}" - name: Check Dockerfile exists id: check-dockerfile run: | cd target_repo if [ ! -f "Dockerfile" ]; then echo "错误: 仓库根目录下未找到 Dockerfile" exit 1 fi echo "Dockerfile 存在" - name: Login to Docker Registry id: login-registry if: github.event.inputs.dry_run != 'true' run: | echo "$DOCKER_PASSWORD" | docker login "$DOCKER_REGISTRY" \ -u "$DOCKER_USERNAME" \ --password-stdin echo "Docker 登录成功" - name: Build Docker Image id: build-image run: | cd target_repo echo "开始构建镜像: ${IMAGE}" set -o pipefail docker build -t "${IMAGE}" . 2>&1 | tee build.log echo "镜像构建完成" - name: List built image run: | docker images | grep "${IMAGE_NAME}" || echo "镜像列表查看完成" - name: Push Docker Image id: push-image if: github.event.inputs.dry_run != 'true' run: | set -o pipefail { for attempt in 1 2 3; do echo "Starting docker push attempt ${attempt}/3 for ${IMAGE}" if docker push "${IMAGE}"; then echo "docker push completed successfully" exit 0 fi echo "docker push failed on attempt ${attempt}/3" if [ $attempt -lt 3 ]; then echo "等待 30 秒后重试..." sleep 30 fi done echo "docker push failed after 3 attempts" exit 1 } 2>&1 | tee push.log - name: Dry Run Summary if: github.event.inputs.dry_run == 'true' run: | echo "==========================================" echo "✅ 仅构建模式完成(未推送)" echo "==========================================" echo "源仓库: ${{ github.event.inputs.repo_url }}" echo "分支: ${{ github.event.inputs.ref }}" echo "镜像名称: ${IMAGE}" echo "镜像标签: ${SAFE_TAG}" echo "==========================================" echo "如需完整发布(推送镜像),请使用 dry_run=false 重新触发" - name: Full Build Summary if: github.event.inputs.dry_run != 'true' run: | echo "==========================================" echo "✅ 完整构建完成(已推送)" echo "==========================================" echo "源仓库: ${{ github.event.inputs.repo_url }}" echo "分支: ${{ github.event.inputs.ref }}" echo "镜像名称: ${IMAGE}" echo "镜像标签: ${SAFE_TAG}" echo "==========================================" - name: Callback notification if: always() && github.event.inputs.callback_url != '' run: | echo "正在发送回调通知..." # 构造回调请求体 CALLBACK_URL="${{ github.event.inputs.callback_url }}" CALLBACK_TOKEN="${{ github.event.inputs.callback_token }}" JOB_ID="${{ github.event.inputs.job_id }}" STARTUP_PARAMS='${{ github.event.inputs.startup_params }}' # 判断状态与错误信息 STATUS="SUCCESS" ERR_MESSAGE="" if [ "${{ steps.check-dockerfile.outcome }}" == "failure" ]; then STATUS="FAILED" ERR_MESSAGE="仓库根目录下未找到 Dockerfile" elif [ "${{ steps.login-registry.outcome }}" == "failure" ]; then STATUS="FAILED" ERR_MESSAGE="Docker 镜像仓库登录失败" elif [ "${{ steps.build-image.outcome }}" == "failure" ]; then STATUS="FAILED" ERR_MESSAGE=$(tail -n 20 build.log 2>/dev/null || echo "Docker 镜像构建失败") elif [ "${{ steps.push-image.outcome }}" == "failure" ]; then STATUS="FAILED" ERR_MESSAGE=$(tail -n 20 push.log 2>/dev/null || echo "Docker 镜像推送失败") fi # 使用 jq 构造 JSON 请求体 if [ -n "$ERR_MESSAGE" ]; then BODY=$(jq -n \ --arg jobId "$JOB_ID" \ --arg imageUrl "$IMAGE" \ --arg status "$STATUS" \ --arg errMessage "$ERR_MESSAGE" \ --argjson startupParams "$STARTUP_PARAMS" \ '{jobId: $jobId, imageUrl: $imageUrl, status: $status, errMessage: $errMessage, startupParams: $startupParams}') else BODY=$(jq -n \ --arg jobId "$JOB_ID" \ --arg imageUrl "$IMAGE" \ --arg status "$STATUS" \ --argjson startupParams "$STARTUP_PARAMS" \ '{jobId: $jobId, imageUrl: $imageUrl, status: $status, startupParams: $startupParams}') fi echo "回调请求体: $BODY" # 发送 POST 请求 HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ -X POST \ -H "Content-Type: application/json" \ -H "X-Callback-Token: $CALLBACK_TOKEN" \ -d "$BODY" \ "$CALLBACK_URL" || true) # 去除空白并校验 HTTP_CODE 是否为数字 HTTP_CODE=$(echo "$HTTP_CODE" | tr -d '[:space:]') if ! [[ "$HTTP_CODE" =~ ^[0-9]+$ ]]; then HTTP_CODE="000" fi if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then echo "✅ 回调通知发送成功,HTTP 状态码: $HTTP_CODE" else echo "❌ 回调通知发送失败,HTTP 状态码: $HTTP_CODE" exit 1 fi