From 8ba911a119908a830fb4ed97a6dec933b355997a Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 17 Sep 2026 16:56:38 +0700 Subject: [PATCH] =?UTF-8?q?CD:=20uat.yml=20=E0=B9=84=E0=B8=A1=E0=B9=88=20b?= =?UTF-8?q?uild=20image=20=E0=B9=80=E0=B8=AD=E0=B8=87=20reuse=20=E0=B8=82?= =?UTF-8?q?=E0=B8=AD=E0=B8=87=20build.yml=20+=20=E0=B8=82=E0=B8=B6?= =?UTF-8?q?=E0=B9=89=E0=B8=99=20UAT=20=E0=B8=9C=E0=B9=88=E0=B8=B2=E0=B8=99?= =?UTF-8?q?=20Forgejo=20UI=20=E0=B9=84=E0=B8=94=E0=B9=89=20=E2=80=94=20?= =?UTF-8?q?=E0=B8=A5=E0=B8=9A=20job=20build=20=E0=B8=97=E0=B8=B1=E0=B9=89?= =?UTF-8?q?=E0=B8=87=20job=20(=E0=B9=80=E0=B8=94=E0=B8=B4=E0=B8=A1=20build?= =?UTF-8?q?=20=E0=B8=8B=E0=B9=89=E0=B8=B3=E0=B8=97=E0=B8=B8=E0=B8=81?= =?UTF-8?q?=E0=B8=84=E0=B8=A3=E0=B8=B1=E0=B9=89=E0=B8=87)=20=E0=B9=81?= =?UTF-8?q?=E0=B8=97=E0=B8=99=E0=B8=94=E0=B9=89=E0=B8=A7=E0=B8=A2=20steps?= =?UTF-8?q?=20=E0=B9=83=E0=B8=AB=E0=B8=A1=E0=B9=88=E0=B9=83=E0=B8=99=20dep?= =?UTF-8?q?loy=20job:=20Resolve=20image=20tag=20map=20deploy1.2.3/1.2.3/v1?= =?UTF-8?q?.2.3=20=E2=86=92=20v1.2.3=20=E0=B9=80=E0=B8=AA=E0=B8=A1?= =?UTF-8?q?=E0=B8=AD=20(=E0=B8=95=E0=B8=B1=E0=B8=94=20prefix=20deploy=20?= =?UTF-8?q?=E0=B8=95=E0=B8=B1=E0=B8=94=20v=20=E0=B9=80=E0=B8=95=E0=B8=B4?= =?UTF-8?q?=E0=B8=A1=20v=20=E0=B8=81=E0=B8=A5=E0=B8=B1=E0=B8=9A=20?= =?UTF-8?q?=E2=80=94=20=E0=B9=83=E0=B8=8A=E0=B9=89=20image=20=E0=B9=80?= =?UTF-8?q?=E0=B8=94=E0=B8=B5=E0=B8=A2=E0=B8=A7=E0=B8=81=E0=B8=B1=E0=B8=9A?= =?UTF-8?q?=E0=B8=97=E0=B8=B5=E0=B9=88=20build.yml=20push=20=E0=B8=95?= =?UTF-8?q?=E0=B8=AD=E0=B8=99=20tag=20v*)=20+=20login=20registry=20+=20doc?= =?UTF-8?q?ker=20manifest=20inspect=20=E0=B9=80=E0=B8=8A=E0=B9=87=E0=B8=84?= =?UTF-8?q?=20image=20=E0=B8=A1=E0=B8=B5=E0=B8=88=E0=B8=A3=E0=B8=B4?= =?UTF-8?q?=E0=B8=87=E0=B8=81=E0=B9=88=E0=B8=AD=E0=B8=99=20SSH=20(fail-fas?= =?UTF-8?q?t=20=E0=B8=9E=E0=B8=A3=E0=B9=89=E0=B8=AD=E0=B8=A1=E0=B8=82?= =?UTF-8?q?=E0=B9=89=E0=B8=AD=E0=B8=84=E0=B8=A7=E0=B8=B2=E0=B8=A1=E0=B8=9A?= =?UTF-8?q?=E0=B8=AD=E0=B8=81=E0=B9=83=E0=B8=AB=E0=B9=89=20push=20tag=20v*?= =?UTF-8?q?=20=E0=B8=81=E0=B9=88=E0=B8=AD=E0=B8=99=20=E0=B8=96=E0=B9=89?= =?UTF-8?q?=E0=B8=B2=E0=B8=A2=E0=B8=B1=E0=B8=87=E0=B9=84=E0=B8=A1=E0=B9=88?= =?UTF-8?q?=E0=B8=A1=E0=B8=B5)=20+=20workflow=5Fdispatch=20=E0=B9=80?= =?UTF-8?q?=E0=B8=9E=E0=B8=B4=E0=B9=88=E0=B8=A1=20input=20version=20(requi?= =?UTF-8?q?red)=20=E2=80=94=20=E0=B8=81=E0=B8=94=20Run=20workflow=20?= =?UTF-8?q?=E0=B9=83=E0=B8=99=20UI=20=E0=B9=83=E0=B8=AA=E0=B9=88=E0=B9=80?= =?UTF-8?q?=E0=B8=A7=E0=B8=AD=E0=B8=A3=E0=B9=8C=E0=B8=8A=E0=B8=B1=E0=B8=99?= =?UTF-8?q?=E0=B9=84=E0=B8=94=E0=B9=89=E0=B9=80=E0=B8=A5=E0=B8=A2=E0=B9=84?= =?UTF-8?q?=E0=B8=A1=E0=B9=88=E0=B8=95=E0=B9=89=E0=B8=AD=E0=B8=87=E0=B8=AA?= =?UTF-8?q?=E0=B8=A3=E0=B9=89=E0=B8=B2=E0=B8=87=20tag=20deploy*=20(?= =?UTF-8?q?=E0=B8=97=E0=B8=B2=E0=B8=87=E0=B9=80=E0=B8=94=E0=B8=B4=E0=B8=A1?= =?UTF-8?q?=E0=B8=A2=E0=B8=B1=E0=B8=87=E0=B9=83=E0=B8=8A=E0=B9=89=E0=B9=84?= =?UTF-8?q?=E0=B8=94=E0=B9=89)=20=E2=80=94=20deploy=20job=20=E0=B9=80?= =?UTF-8?q?=E0=B8=9B=E0=B8=B4=E0=B8=94=20if=20=E0=B9=83=E0=B8=AB=E0=B9=89?= =?UTF-8?q?=E0=B8=A3=E0=B8=B1=E0=B8=99=E0=B8=81=E0=B8=A3=E0=B8=93=E0=B9=89?= =?UTF-8?q?=20dispatch=20(always()=20&&=20!failure()=20&&=20!cancelled()?= =?UTF-8?q?=20=E2=80=94=20=E0=B8=9C=E0=B9=88=E0=B8=B2=E0=B8=99=E0=B9=80?= =?UTF-8?q?=E0=B8=A1=E0=B8=B7=E0=B9=88=E0=B8=AD=20validate-schema=20succes?= =?UTF-8?q?s=20=E0=B8=AB=E0=B8=A3=E0=B8=B7=E0=B8=AD=20skip=20=E0=B9=81?= =?UTF-8?q?=E0=B8=95=E0=B9=88=E0=B8=A2=E0=B8=B1=E0=B8=87=20block=20?= =?UTF-8?q?=E0=B8=96=E0=B9=89=E0=B8=B2=20fail=20=E0=B9=80=E0=B8=AB?= =?UTF-8?q?=E0=B8=A1=E0=B8=B7=E0=B8=AD=E0=B8=99=E0=B9=80=E0=B8=94=E0=B8=B4?= =?UTF-8?q?=E0=B8=A1)=20+=20notify=20=E0=B8=96=E0=B8=AD=E0=B8=94=20needs.b?= =?UTF-8?q?uild=20=E0=B8=97=E0=B8=B5=E0=B9=88=E0=B8=A5=E0=B8=9A=E0=B9=84?= =?UTF-8?q?=E0=B8=9B=20+=20field=20Tag=20=E0=B9=82=E0=B8=8A=E0=B8=A7?= =?UTF-8?q?=E0=B9=8C=E0=B9=80=E0=B8=A7=E0=B8=AD=E0=B8=A3=E0=B9=8C=E0=B8=8A?= =?UTF-8?q?=E0=B8=B1=E0=B8=99=E0=B8=88=E0=B8=B2=E0=B8=81=20input=20?= =?UTF-8?q?=E0=B8=95=E0=B8=AD=E0=B8=99=20dispatch=20=E0=B9=81=E0=B8=97?= =?UTF-8?q?=E0=B8=99=E0=B8=8A=E0=B8=B7=E0=B9=88=E0=B8=AD=20branch=20+=20fi?= =?UTF-8?q?eld=20Image=20Tag=20=E0=B8=94=E0=B8=B6=E0=B8=87=E0=B8=88?= =?UTF-8?q?=E0=B8=B2=E0=B8=81=20output=20=E0=B8=82=E0=B8=AD=E0=B8=87=20dep?= =?UTF-8?q?loy=20job=20=E2=80=94=20=E0=B8=81=E0=B8=95=E0=B8=B4=E0=B8=81?= =?UTF-8?q?=E0=B8=B2=E0=B8=84=E0=B8=87=E0=B9=80=E0=B8=94=E0=B8=B4=E0=B8=A1?= =?UTF-8?q?:=20image=20=E0=B9=80=E0=B8=81=E0=B8=B4=E0=B8=94=E0=B8=88?= =?UTF-8?q?=E0=B8=B2=E0=B8=81=20build.yml=20=E0=B8=95=E0=B8=AD=E0=B8=99=20?= =?UTF-8?q?push=20tag=20v*=20(deploy=20Test=20=E0=B9=84=E0=B8=9B=E0=B8=94?= =?UTF-8?q?=E0=B9=89=E0=B8=A7=E0=B8=A2)=20=E0=B9=80=E0=B8=97=E0=B9=88?= =?UTF-8?q?=E0=B8=B2=E0=B8=99=E0=B8=B1=E0=B9=89=E0=B8=99=20UAT=20=E0=B9=81?= =?UTF-8?q?=E0=B8=84=E0=B9=88=20reuse=20=E2=80=94=20verify:=20js-yaml=20pa?= =?UTF-8?q?rse=20=E0=B8=9C=E0=B9=88=E0=B8=B2=E0=B8=99=20+=20=E0=B8=97?= =?UTF-8?q?=E0=B8=94=E0=B8=AA=E0=B8=AD=E0=B8=9A=20normalize=20=E0=B8=84?= =?UTF-8?q?=E0=B8=A3=E0=B8=9A=203=20=E0=B8=A3=E0=B8=B9=E0=B8=9B=E0=B9=81?= =?UTF-8?q?=E0=B8=9A=E0=B8=9A=20(deploy1.2.3/v1.2.3/1.2.3=20=E2=86=92=20v1?= =?UTF-8?q?.2.3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Code --- .forgejo/workflows/uat.yml | 84 ++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/.forgejo/workflows/uat.yml b/.forgejo/workflows/uat.yml index a09aa77..e38514c 100644 --- a/.forgejo/workflows/uat.yml +++ b/.forgejo/workflows/uat.yml @@ -5,6 +5,10 @@ on: tags: - "deploy*.*.*" workflow_dispatch: + inputs: + version: + description: "เวอร์ชันที่จะ deploy เช่น 1.2.3 / v1.2.3 / deploy1.2.3 — จะ map เป็น image v1.2.3 (ต้อง push tag v1.2.3 ให้ build.yml build ไว้ก่อน)" + required: true env: # Container Registry @@ -12,11 +16,11 @@ env: REGISTRY_USERNAME: ${{ vars.CONTAINER_REGISTRY_USERNAME }} REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} - # Docker Image + # Docker Image — ไม่ build เอง ใช้ image เดียวกับที่ build.yml push ไว้ + # (push tag deploy1.2.3 หรือ Run workflow จาก UI ใส่เวอร์ชัน → map เป็น image v1.2.3 เสมอ) WEB_IMAGE_NAME: chamomind/ecourt-note - IMAGE_TAG: ${{ github.ref_name }} - # SSH Deployment — ต่างจาก build.yml ตัวเดียว: ชี้เครื่อง UAT (ตั้ง vars.SSH_UAT_DEPLOY_HOST เพิ่มใน Forgejo) + # SSH Deployment — ต่างจาก build.yml: ชี้เครื่อง UAT (ตั้ง vars.SSH_UAT_DEPLOY_HOST เพิ่มใน Forgejo) + ไม่ build image เอง DEPLOY_HOST: ${{ vars.SSH_UAT_DEPLOY_HOST }} DEPLOY_USER: ${{ vars.SSH_DEPLOY_USER }} @@ -85,14 +89,28 @@ jobs: echo "" echo "📚 Reference: README.md / docs/PLAN.md §3" - build: - name: Build Web Docker Image + deploy: + name: Deploy eCourt Note Web to UAT Server runs-on: ubuntu-latest needs: validate-schema - if: startsWith(github.ref, 'refs/tags/') + # รันได้ 2 ทาง: push tag deploy*.*.* หรือกด Run workflow จาก UI (ใส่เวอร์ชันใน input) + # !failure() && !cancelled() = ผ่านได้เมื่อ validate-schema success หรือถูก skip (กรณี dispatch) + # แต่ยังไม่รันถ้า validate-schema fail (tag ไม่อยู่บน dev) + if: >- + always() && !failure() && !cancelled() && + (startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch') + outputs: + image_tag: ${{ steps.image-tag.outputs.tag }} steps: - - name: Checkout code - uses: actions/checkout@v4 + - name: Resolve image tag (deploy1.2.3 / v1.2.3 → v1.2.3) + id: image-tag + run: | + RAW="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version || github.ref_name }}" + TAG="${RAW#deploy}" + TAG="${TAG#v}" + TAG="v$TAG" + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "Reuse image จาก build.yml: ${{ env.REGISTRY_URL }}/${{ env.WEB_IMAGE_NAME }}:$TAG" - name: Log in to Container Registry uses: docker/login-action@v3 @@ -101,39 +119,15 @@ jobs: username: ${{ env.REGISTRY_USERNAME }} password: ${{ env.REGISTRY_PASSWORD }} - - name: Build Docker image (no buildx) + - name: Verify image exists in registry run: | - # Build using standard docker build (not buildx) - git reset --hard HEAD - cd ${{ github.workspace }} - docker build \ - --no-cache \ - --pull \ - --file ./docker/web.Dockerfile \ - --tag ${{ env.REGISTRY_URL }}/${{ env.WEB_IMAGE_NAME }}:${{ env.IMAGE_TAG }} \ - --tag ${{ env.REGISTRY_URL }}/${{ env.WEB_IMAGE_NAME }}:latest \ - . + if ! docker manifest inspect "${{ env.REGISTRY_URL }}/${{ env.WEB_IMAGE_NAME }}:${{ steps.image-tag.outputs.tag }}" > /dev/null; then + echo "❌ Image ${{ env.REGISTRY_URL }}/${{ env.WEB_IMAGE_NAME }}:${{ steps.image-tag.outputs.tag }} ไม่มีใน registry" + echo "workflow นี้ไม่ build เอง — push tag ${{ steps.image-tag.outputs.tag }} ก่อน เพื่อให้ build.yml build + push image ให้" + exit 1 + fi + echo "✅ Image found — deploy ด้วย image เดียวกับ build.yml" - echo "✅ Build successful!" - docker images | grep ${{ env.WEB_IMAGE_NAME }} - - - name: Push Docker image - run: | - docker push ${{ env.REGISTRY_URL }}/${{ env.WEB_IMAGE_NAME }}:${{ env.IMAGE_TAG }} - docker push ${{ env.REGISTRY_URL }}/${{ env.WEB_IMAGE_NAME }}:latest - echo "✅ Image pushed to registry" - - - name: Test image can run - run: | - # Quick test that image can start - docker run --rm ${{ env.REGISTRY_URL }}/${{ env.WEB_IMAGE_NAME }}:${{ env.IMAGE_TAG }} node --version - - deploy: - name: Deploy eCourt Note Web to UAT Server - runs-on: ubuntu-latest - needs: build - if: startsWith(github.ref, 'refs/tags/') - steps: - name: Deploy web on UAT server uses: appleboy/ssh-action@v1.2.1 with: @@ -145,13 +139,13 @@ jobs: cd ~/repo chmod +x ./replace-env.sh ./deploy.sh - ./replace-env.sh ECOURT_NOTE_TAG "${{ env.IMAGE_TAG }}" + ./replace-env.sh ECOURT_NOTE_TAG "${{ steps.image-tag.outputs.tag }}" ./deploy.sh notify: name: Notify Deployment Status runs-on: ubuntu-latest - needs: [build, deploy] + needs: [deploy] if: always() steps: - name: Send Discord notification - Success @@ -171,7 +165,7 @@ jobs: }, { "name": "Tag", - "value": "${{ github.ref_name }}", + "value": "${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version || github.ref_name }}", "inline": true }, { @@ -181,7 +175,7 @@ jobs: }, { "name": "Image Tag", - "value": "`${{ env.IMAGE_TAG }}`", + "value": "`${{ needs.deploy.outputs.image_tag }}`", "inline": true }, { @@ -204,7 +198,7 @@ jobs: ${{ env.DISCORD_WEBHOOK_URL }} - name: Send Discord notification - Failure - if: ${{ needs.deploy.result == 'failure' || needs.build.result == 'failure' }} + if: ${{ needs.deploy.result == 'failure' }} run: | curl -H "Content-Type: application/json" \ -d '{ @@ -230,7 +224,7 @@ jobs: }, { "name": "Failed Job", - "value": "${{ needs.build.result == 'failure' && 'Build' || 'Deploy' }}", + "value": "Deploy", "inline": true }, {