no message #151
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI Build and Push | |
| # | |
| # 必須在 GitHub 專案設定 → Settings → Secrets and variables → Actions 中建立: | |
| # 1. DOCKERHUB_USERNAME:可推送映像到 Docker Hub 的帳號。 | |
| # 2. DOCKERHUB_TOKEN:對應帳號的存取 token/密碼。 | |
| # 3. GH_PAT:至少擁有 repo 權限的 Personal Access Token,用來更新 GitOps 儲庫。 | |
| # | |
| on: | |
| push: | |
| # branches: [ "disable" ] | |
| branches: ["**"] | |
| # 任何分支推送都觸發流程 | |
| # 集中管理各微服務目錄與對應的變更偵測 | |
| env: | |
| FORCE_BUILD_ALL: "false" # 設為 true 時視為所有服務皆有變動 | |
| SERVICE_DIRS: | # 需要監控的微服務資料夾 | |
| service/service-test | |
| service/service-template | |
| SERVICE_GLOBS: | # 用於變更偵測的檔案範圍 | |
| service/service-test/** | |
| service/service-template/** | |
| jobs: | |
| detect-changes: | |
| runs-on: ubuntu-latest | |
| # 以 Ubuntu Runner 執行變更偵測 | |
| outputs: | |
| matrix: ${{ steps.build-matrix.outputs.matrix }} | |
| has_changes: ${{ steps.build-matrix.outputs.has_changes }} | |
| # 將偵測結果輸出給後續 Job 使用 | |
| steps: | |
| # 取得完整 Git 歷史作後續比較 | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # 判斷有哪些微服務有變動,後續工作只處理必要項目。 | |
| - name: Detect changed microservices | |
| id: changed | |
| uses: tj-actions/changed-files@v45 | |
| with: | |
| files: ${{ env.SERVICE_GLOBS }} | |
| # 列出所有變更檔案供除錯參考 | |
| - name: Log changed files | |
| if: steps.changed.outputs.all_changed_files != '' | |
| run: | | |
| echo "::group::Changed files" | |
| echo "${{ steps.changed.outputs.all_changed_files }}" | tr ' ' '\n' | |
| echo "::endgroup::" | |
| # 將變動檔案列表轉成矩陣格式,供後續 Job 動態決策。 | |
| - name: Build service matrix | |
| id: build-matrix | |
| run: | | |
| set -euo pipefail | |
| # 確保腳本遇到錯誤立即停止 | |
| echo "Building matrix from detected changes..." | |
| # 依據設定的服務清單建立陣列 | |
| readarray -t services <<< "${SERVICE_DIRS}" | |
| declare -A seen=() | |
| matrix_entries=() | |
| # seen 用來去除重複服務 | |
| if [[ "${FORCE_BUILD_ALL}" == "true" ]]; then | |
| echo "::notice::FORCE_BUILD_ALL is true; scheduling all services." | |
| for svc in "${services[@]}"; do | |
| [[ -z "$svc" ]] && continue | |
| if [[ -z ${seen[$svc]:-} ]]; then | |
| seen[$svc]=1 | |
| name=$(basename "$svc") | |
| slug=$(echo "$name" | tr '[:upper:]' '[:lower:]') | |
| matrix_entries+=("{\"service\":\"$svc\",\"name\":\"$name\",\"image_slug\":\"$slug\"}") | |
| fi | |
| done | |
| else | |
| # 掃描所有變動檔案並比對服務前綴 | |
| while IFS= read -r file; do | |
| for svc in "${services[@]}"; do | |
| [[ -z "$svc" ]] && continue | |
| prefix="$svc/" | |
| if [[ $file == ${prefix}* && -z ${seen[$svc]:-} ]]; then | |
| seen[$svc]=1 | |
| name=$(basename "$svc") | |
| slug=$(echo "$name" | tr '[:upper:]' '[:lower:]') | |
| matrix_entries+=("{\"service\":\"$svc\",\"name\":\"$name\",\"image_slug\":\"$slug\"}") | |
| # 收集後續矩陣所需欄位 | |
| fi | |
| done | |
| done < <(printf "%s\n%s\n" "${{ steps.changed.outputs.modified_files }}" "${{ steps.changed.outputs.added_files }}" | tr ' ' '\n' | sort -u) | |
| fi | |
| if [ ${#matrix_entries[@]} -eq 0 ]; then | |
| # 無服務變更 | |
| echo "has_changes=false" >> "$GITHUB_OUTPUT" | |
| echo 'matrix={"include":[]}' >> "$GITHUB_OUTPUT" | |
| echo "::notice::No microservice changes detected; downstream jobs will be skipped." | |
| else | |
| # 有至少一個服務需要建置 | |
| echo "has_changes=true" >> "$GITHUB_OUTPUT" | |
| joined_entries=$(IFS=, ; echo "${matrix_entries[*]}") | |
| matrix_json="{\"include\":[${joined_entries}]}" | |
| echo "matrix=${matrix_json}" >> "$GITHUB_OUTPUT" | |
| echo "Matrix definition: ${matrix_json}" | |
| fi | |
| build-and-push: | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.has_changes == 'true' | |
| # 僅在前段偵測到變更時才執行 | |
| runs-on: ubuntu-latest | |
| # 使用預設 Ubuntu Runner 進行建置 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.detect-changes.outputs.matrix) }} | |
| # 對每個受影響服務個別建置,互不阻塞 | |
| env: | |
| TZ: Asia/Taipei | |
| # 固定時區;mvnd native 版本不支援停用 daemon | |
| steps: | |
| # 取得變更服務的原始碼 | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # 準備 JDK,並啟用 Maven 依賴快取 | |
| - name: Provision Java toolchain | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '23' | |
| cache: maven | |
| # 快取 mvnd 發行版,避免每次重新下載 | |
| - name: Cache mvnd distribution | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ env.HOME }}/.mvnd | |
| key: mvnd-${{ runner.os }}-2.0.0-rc-3 | |
| # 下載並安裝 mvnd,用於加速 Maven 構建 | |
| - name: Install mvnd | |
| run: | | |
| set -euo pipefail | |
| # 嚴格模式確保安裝流程安全 | |
| MVND_VERSION=2.0.0-rc-3 | |
| if [ ! -x "$HOME/.mvnd/bin/mvnd" ]; then | |
| ARCHIVE_BASE="maven-mvnd-${MVND_VERSION}-linux-amd64" | |
| ARCHIVE_FILE="${ARCHIVE_BASE}.tar.gz" | |
| DOWNLOAD_URL="https://downloads.apache.org/maven/mvnd/${MVND_VERSION}/${ARCHIVE_FILE}" | |
| curl -fsSL "$DOWNLOAD_URL" -o mvnd.tar.gz | |
| tar -xzf mvnd.tar.gz | |
| rm -rf "$HOME/.mvnd" | |
| mv "$ARCHIVE_BASE" "$HOME/.mvnd" | |
| rm mvnd.tar.gz | |
| fi | |
| echo "$HOME/.mvnd/bin" >> $GITHUB_PATH | |
| # 僅為目標微服務建置,`-am` 會帶入依賴模組。 | |
| # 使用 mvnd 並行打包,提高 CI 構建速度 | |
| - name: Build service artifact | |
| env: | |
| IMAGE_TAG: ${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| # 確保 mvnd 指令出錯時立即失敗 | |
| svc="${{ matrix.service }}" | |
| echo "::group::Building Maven artifacts for ${svc}" | |
| mvnd -B -T 1C -Dmaven.compiler.useIncrementalCompilation=false -DskipTests -Duser.timezone=Asia/Taipei -Dimage.tag=${IMAGE_TAG} -pl "$svc" -am clean package | |
| # -T 1C 會依 CPU 自動分派執行緒 | |
| echo "::endgroup::" | |
| # 啟用 QEMU 以支援跨架構建置 | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v3 | |
| with: | |
| platforms: linux/arm64 | |
| # 建立 Docker Buildx Builder(container driver 支援多架構) | |
| - name: Set up Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| with: | |
| install: true | |
| cleanup: false | |
| # 登入 Docker Hub 推送映像 | |
| - name: Login to DockerHub | |
| uses: docker/login-action@v3 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| # 建置並推送映像,使用短 SHA 與 latest 雙標籤。 | |
| - name: Build and push image | |
| env: | |
| IMAGE_REPO: ${{ secrets.DOCKERHUB_USERNAME }} | |
| run: | | |
| set -euo pipefail | |
| # Docker 建置失敗時立即終止 | |
| svc="${{ matrix.service }}" | |
| name="${{ matrix.name }}" | |
| slug="${{ matrix.image_slug }}" | |
| image="${IMAGE_REPO}/$slug" | |
| short_sha="${GITHUB_SHA::7}" | |
| cache_scope="svc-${slug}" | |
| echo "Building docker image ${image}:${short_sha} from ${svc}" | |
| echo "::group::Docker build logs" | |
| docker buildx build "$svc" \ | |
| --file "$svc/Dockerfile" \ | |
| --platform linux/amd64,linux/arm64 \ | |
| --tag "$image:$short_sha" \ | |
| --tag "$image:latest" \ | |
| --cache-from "type=gha,scope=${cache_scope}" \ | |
| --cache-to "type=gha,scope=${cache_scope},mode=max" \ | |
| --push | |
| echo "::endgroup::" | |
| # 拉取 GitOps 倉庫準備更新映像標籤 | |
| - name: Checkout GitOps repo | |
| if: github.event_name == 'push' | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ github.repository_owner }}/GitOps | |
| token: ${{ secrets.GH_PAT }} | |
| path: gitops | |
| # 更新對應服務的部署檔,讓 ArgoCD 拉取最新映像 | |
| - name: Update image tag in manifests | |
| if: github.event_name == 'push' | |
| id: manifest | |
| working-directory: gitops | |
| env: | |
| IMAGE_REPO: ${{ secrets.DOCKERHUB_USERNAME }} | |
| SERVICE_NAME: ${{ matrix.name }} | |
| IMAGE_SLUG: ${{ matrix.image_slug }} | |
| run: | | |
| set -eo pipefail | |
| # 確保更新 manifest 過程出錯時即中止 | |
| short_sha="${GITHUB_SHA::7}" | |
| image="${IMAGE_REPO}/${IMAGE_SLUG}:$short_sha" | |
| deployment_path="k8s/${SERVICE_NAME}/deployment.yaml" | |
| if [ ! -f "$deployment_path" ]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "Missing deployment file for ${SERVICE_NAME}, nothing to update." | |
| exit 0 | |
| fi | |
| echo "Updating ${deployment_path} to image ${image}" | |
| sed -i "s|image: .*${IMAGE_SLUG}:.*|image: ${image}|" "$deployment_path" | |
| echo "deployment_path=$deployment_path" >> "$GITHUB_OUTPUT" | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| # 將更新後的 manifests 提交回 GitOps 倉庫 | |
| - name: Commit and push manifest changes | |
| if: github.event_name == 'push' && steps.manifest.outputs.skip == 'false' | |
| uses: EndBug/add-and-commit@v9 | |
| with: | |
| cwd: gitops | |
| add: ${{ steps.manifest.outputs.deployment_path }} | |
| message: "Update ${{ matrix.name }} image to ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_slug }}:${{ github.sha }}" | |
| push: true | |
| github_token: ${{ secrets.GH_PAT }} |