Skip to content

refactor(commerce): member·wishlist 흡수 — 6-도메인 모놀리스 (ADR-0058 R2) #123

refactor(commerce): member·wishlist 흡수 — 6-도메인 모놀리스 (ADR-0058 R2)

refactor(commerce): member·wishlist 흡수 — 6-도메인 모놀리스 (ADR-0058 R2) #123

Workflow file for this run

name: images
on:
push:
branches: [main]
tags: ["v*"]
workflow_dispatch:
inputs:
registry:
description: "Override registry (default: <region>.ocir.io/<namespace>)"
required: false
default: ""
rebuild_all:
description: "변경 감지 무시하고 전 서비스 재빌드 (ADR-0058 commerce 폴드 후 JVM 17 + FE 8 = 25)"
required: false
default: "false"
# Only one image-publish run at a time per ref.
# cancel-in-progress: false — OCIR push 중 cancel 시 partial manifest 위험.
# 대신 job-level timeout-minutes 로 hang 방지.
concurrency:
group: images-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write # manifest commit-back
jobs:
publish:
name: Build & push arm64 images to OCIR + update oci-arm manifest
# arm64 native runner — QEMU 에뮬레이션 없음 → Node 24 V8 JIT 충돌 해소.
# msa repo 가 public 이라 free unlimited. amd64 보다 5~10배 빠름.
runs-on: ubuntu-24.04-arm
timeout-minutes: 60 # hang 시 6h default → 60m 로 단축
# `[skip ci]` prevent self-trigger on commit-back
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }}
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
# 최소권한: 이 워크플로가 접근하는 3개 repo 만 명시.
# App 설치 페이지에 msa / msa-auth / msa-gifticon 셋 모두 있어야 동작.
repositories: |
msa
msa-auth
msa-gifticon
- name: Checkout (root only)
uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
submodules: false
fetch-depth: 0
- name: Init build-required submodules (auth, gifticon)
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
git config --global \
url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf \
"https://github.com/"
git submodule update --init --recursive -- auth gifticon
# ───── 변경된 서비스 감지 ──────────────────────────────────────────────
# 입력값: git diff HEAD~1..HEAD (직전 commit 대비) 또는 force rebuild.
# 출력: jvm_services, docker_services, kustomize_services 의 space-separated 목록.
# 공유 의존성(common/buildSrc/gradle 등) 변경 시 모든 backend 강제 rebuild.
- name: Detect changed services
id: changes
env:
REBUILD_ALL: ${{ github.event.inputs.rebuild_all }}
GITHUB_EVENT_BEFORE: ${{ github.event.before }}
run: |
set -euo pipefail
ALL_JVM="gateway product search search-batch search-consumer auth gifticon analytics experiment code-dictionary commerce chatbot quant recommendation agent-viewer-api"
ALL_DOCKER="admin-fe gifticon-fe agent-viewer-fe portal-fe quant-fe quant-ingest recommendation-ml recommendation-ann"
# 강제 rebuild 트리거
if [[ "$REBUILD_ALL" == "true" ]] \
|| [[ "$GITHUB_EVENT_BEFORE" == "0000000000000000000000000000000000000000" ]] \
|| [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "→ 전체 rebuild 모드 (force / first push / version tag)"
echo "jvm_services=$ALL_JVM" >> "$GITHUB_OUTPUT"
echo "docker_services=$ALL_DOCKER" >> "$GITHUB_OUTPUT"
echo "kustomize_services=$ALL_JVM $ALL_DOCKER" >> "$GITHUB_OUTPUT"
exit 0
fi
# before SHA 가 비어있거나 잘못된 경우 fallback
if ! git cat-file -e "$GITHUB_EVENT_BEFORE" 2>/dev/null; then
echo "→ before SHA unreachable, 전체 rebuild fallback"
echo "jvm_services=$ALL_JVM" >> "$GITHUB_OUTPUT"
echo "docker_services=$ALL_DOCKER" >> "$GITHUB_OUTPUT"
echo "kustomize_services=$ALL_JVM $ALL_DOCKER" >> "$GITHUB_OUTPUT"
exit 0
fi
CHANGED_FILES=$(git diff --name-only "$GITHUB_EVENT_BEFORE" HEAD)
echo "── 변경 파일 ($(echo "$CHANGED_FILES" | wc -l) 개):"
echo "$CHANGED_FILES" | sed 's/^/ /'
# 공유 의존성 변경 시 모든 JVM rebuild
if echo "$CHANGED_FILES" | grep -qE '^(common/|buildSrc/|gradle/|build\.gradle\.kts$|settings\.gradle\.kts$|gradle\.properties$|gradle/libs\.versions\.toml$)'; then
echo "→ 공유 의존성 변경 감지 — 전체 JVM rebuild"
JVM_SVCS="$ALL_JVM"
else
JVM_SVCS=""
fi
DOCKER_SVCS=""
# 경로별 서비스 매핑.
# submodule pointer bump 는 path 가 'auth' (no slash) 로 나옴 →
# 'auth' 와 'auth/*' 둘 다 매칭해야 함. gifticon 동일.
while IFS= read -r file; do
if [[ -z "$file" ]]; then
continue
fi
case "$file" in
gateway/*) JVM_SVCS+=" gateway" ;;
product/*) JVM_SVCS+=" product" ;;
search/app/*|search/domain/*) JVM_SVCS+=" search" ;;
search/batch/*) JVM_SVCS+=" search-batch" ;;
search/consumer/*) JVM_SVCS+=" search-consumer" ;;
auth|auth/*) JVM_SVCS+=" auth" ;;
gifticon|gifticon/app/*|gifticon/domain/*) JVM_SVCS+=" gifticon" ;;
gifticon/frontend/*) DOCKER_SVCS+=" gifticon-fe" ;;
inventory/*|warehouse/*|fulfillment/*|order/*|member/*|wishlist/*|commerce/*) JVM_SVCS+=" commerce" ;;
analytics/*) JVM_SVCS+=" analytics" ;;
experiment/*) JVM_SVCS+=" experiment" ;;
code-dictionary/*) JVM_SVCS+=" code-dictionary" ;;
chatbot/*) JVM_SVCS+=" chatbot" ;;
quant/app/*|quant/domain/*|quant/glossary*)JVM_SVCS+=" quant" ;;
quant/frontend/*) DOCKER_SVCS+=" quant-fe" ;;
quant/ingest/*) DOCKER_SVCS+=" quant-ingest" ;;
recommendation/app/*|recommendation/domain/*) JVM_SVCS+=" recommendation" ;;
recommendation-ml/*) DOCKER_SVCS+=" recommendation-ml" ;;
recommendation-ann/*) DOCKER_SVCS+=" recommendation-ann" ;;
agent-viewer/api/*|agent-viewer/domain/*) JVM_SVCS+=" agent-viewer-api" ;;
agent-viewer/front/*) DOCKER_SVCS+=" agent-viewer-fe" ;;
admin/frontend/*) DOCKER_SVCS+=" admin-fe" ;;
portal-fe/*) DOCKER_SVCS+=" portal-fe" ;;
# k8s 매니페스트만 변경 → 빌드 무관 (Argo CD 가 직접 sync)
# docs/ai/private/career/ideabank → 빌드 무관
esac
done <<< "$CHANGED_FILES"
# 중복 제거. 빌드 대상이 없을 때도 pipefail 에 걸리지 않아야 한다.
normalize_services() {
printf '%s\n' "$*" | tr ' ' '\n' | awk 'NF' | sort -u | tr '\n' ' ' | sed 's/ $//'
}
JVM_SVCS=$(normalize_services "$JVM_SVCS")
DOCKER_SVCS=$(normalize_services "$DOCKER_SVCS")
echo "→ 빌드 대상 JVM : ${JVM_SVCS:-없음}"
echo "→ 빌드 대상 Docker : ${DOCKER_SVCS:-없음}"
KUSTOMIZE_SVCS=$(normalize_services "$JVM_SVCS $DOCKER_SVCS")
echo "→ manifest 갱신 대상: ${KUSTOMIZE_SVCS:-없음}"
echo "jvm_services=$JVM_SVCS" >> "$GITHUB_OUTPUT"
echo "docker_services=$DOCKER_SVCS" >> "$GITHUB_OUTPUT"
echo "kustomize_services=$KUSTOMIZE_SVCS" >> "$GITHUB_OUTPUT"
- name: 변경 없으면 조기 종료
if: ${{ steps.changes.outputs.kustomize_services == '' }}
run: |
echo "✓ 빌드 대상 서비스 없음 — 워크플로 종료"
echo "변경된 파일이 image 빌드와 무관한 경로 (k8s/, docs/, etc.)" >> "$GITHUB_STEP_SUMMARY"
exit 0
- name: Set up JDK 25
if: ${{ steps.changes.outputs.jvm_services != '' }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "25"
- name: Set up Gradle
if: ${{ steps.changes.outputs.jvm_services != '' }}
uses: gradle/actions/setup-gradle@v4
- name: Set up Docker Buildx (native arm64)
if: ${{ steps.changes.outputs.docker_services != '' }}
uses: docker/setup-buildx-action@v3
with:
driver-opts: image=moby/buildkit:latest
- name: Resolve registry + tag
id: meta
if: ${{ steps.changes.outputs.kustomize_services != '' }}
env:
OCIR_REGION: ${{ secrets.OCIR_REGION }}
OCIR_NAMESPACE: ${{ secrets.OCIR_NAMESPACE }}
run: |
set -euo pipefail
REGISTRY="${{ github.event.inputs.registry }}"
if [[ -z "$REGISTRY" ]]; then
if [[ -z "${OCIR_REGION}" || -z "${OCIR_NAMESPACE}" ]]; then
echo "::error::OCIR_REGION / OCIR_NAMESPACE secrets must be set"
exit 1
fi
REGISTRY="${OCIR_REGION}.ocir.io/${OCIR_NAMESPACE}"
fi
REGISTRY=$(echo "$REGISTRY" | tr '[:upper:]' '[:lower:]')
TAG="${GITHUB_SHA::7}"
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
TAG="${GITHUB_REF#refs/tags/}"
fi
echo "registry=$REGISTRY" | tee -a "$GITHUB_OUTPUT"
echo "tag=$TAG" | tee -a "$GITHUB_OUTPUT"
- name: Authenticate to OCIR
if: ${{ steps.changes.outputs.kustomize_services != '' }}
uses: docker/login-action@v3
with:
registry: ${{ secrets.OCIR_REGION }}.ocir.io
username: ${{ secrets.OCIR_USERNAME }}
password: ${{ secrets.OCIR_TOKEN }}
# ───── 테스트 게이트: 변경된 JVM 서비스만 (이미지 push 전 필수) ─────────
- name: ./gradlew test (변경된 JVM 서비스만)
if: ${{ steps.changes.outputs.jvm_services != '' }}
run: |
set -euo pipefail
TASKS=""
for svc in ${{ steps.changes.outputs.jvm_services }}; do
case "$svc" in
gateway) TASKS+=" :gateway:test" ;;
search) TASKS+=" :search:app:test :search:domain:test" ;;
search-batch) TASKS+=" :search:batch:test" ;;
search-consumer) TASKS+=" :search:consumer:test" ;;
agent-viewer-api) TASKS+=" :agent-viewer:api:test" ;;
# ADR-0058: commerce 는 aggregator(:commerce:app)만 있고 :commerce:domain 없음 →
# 폴드된 4 도메인/feature 테스트를 직접 지정 (default 의 :commerce:domain:test 는 미존재 → CI 실패).
commerce) TASKS+=" :commerce:app:test :inventory:domain:test :inventory:feature:test :warehouse:domain:test :warehouse:feature:test :fulfillment:domain:test :fulfillment:feature:test :order:domain:test :order:feature:test :member:domain:test :member:feature:test :wishlist:domain:test :wishlist:feature:test" ;;
*) TASKS+=" :${svc}:app:test :${svc}:domain:test" ;;
esac
done
echo "── Gradle test tasks: $TASKS"
./gradlew --no-daemon --max-workers=1 $TASKS
# ───── JVM 이미지: 변경된 서비스만 Jib 빌드 (arm64, serial) ─────────────
- name: ./gradlew jib (변경된 JVM 서비스만)
if: ${{ steps.changes.outputs.jvm_services != '' }}
run: |
set -euo pipefail
# 서비스 이름 → Gradle task path 매핑
TASKS=""
for svc in ${{ steps.changes.outputs.jvm_services }}; do
case "$svc" in
gateway) TASKS+=" :gateway:jib" ;;
search) TASKS+=" :search:app:jib" ;;
search-batch) TASKS+=" :search:batch:jib" ;;
search-consumer) TASKS+=" :search:consumer:jib" ;;
agent-viewer-api) TASKS+=" :agent-viewer:api:jib" ;;
*) TASKS+=" :${svc}:app:jib" ;;
esac
done
echo "── Gradle tasks: $TASKS"
./gradlew --no-daemon --max-workers=1 \
-PjibRegistry=${{ steps.meta.outputs.registry }} \
-PjibTag=${{ steps.meta.outputs.tag }} \
$TASKS
# ───── FE/Python 이미지: 변경된 서비스만 buildx (arm64) ─────────────────
- name: Build & push 변경된 Docker 이미지
if: ${{ steps.changes.outputs.docker_services != '' }}
env:
REGISTRY: ${{ steps.meta.outputs.registry }}
TAG: ${{ steps.meta.outputs.tag }}
run: |
set -euo pipefail
# 서비스 이름 → context 경로 매핑
declare -A DOCKER_CTX=(
[admin-fe]="admin/frontend"
[gifticon-fe]="gifticon/frontend"
[agent-viewer-fe]="agent-viewer/front"
[portal-fe]="portal-fe"
[quant-fe]="quant/frontend"
[quant-ingest]="quant/ingest"
[recommendation-ml]="recommendation-ml"
[recommendation-ann]="recommendation-ann"
)
for svc in ${{ steps.changes.outputs.docker_services }}; do
ctx="${DOCKER_CTX[$svc]:-}"
# Dockerfile 부재 시 fail-fast. skip 하면 manifest 의 image tag 는
# 갱신되는데 실제 이미지는 OCIR 에 없어 Argo CD 가 ImagePullBackOff.
if [[ -z "$ctx" ]]; then
echo "::error::$svc 의 context 매핑이 DOCKER_CTX 에 없음"
exit 1
fi
if [[ ! -f "$ctx/Dockerfile" ]]; then
echo "::error::$svc 의 Dockerfile 부재 ($ctx/Dockerfile). 매핑 수정 또는 서비스 정리 필요"
exit 1
fi
echo "── docker buildx build → $REGISTRY/$svc:$TAG"
docker buildx build \
--platform linux/arm64 \
-f "$ctx/Dockerfile" \
-t "$REGISTRY/$svc:$TAG" \
--push \
"$ctx"
done
# ───── kustomization.yaml 의 images: 섹션 부분 갱신 ─────────────────────
# 변경된 서비스의 newTag 만 새 sha 로 update. 나머지 image entry 는 그대로.
- name: Update oci-arm manifest image tags (부분)
if: ${{ steps.changes.outputs.kustomize_services != '' }}
env:
REGISTRY: ${{ steps.meta.outputs.registry }}
TAG: ${{ steps.meta.outputs.tag }}
run: |
set -euo pipefail
if ! command -v yq &>/dev/null; then
curl -sSL -o /tmp/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_arm64
chmod +x /tmp/yq
sudo mv /tmp/yq /usr/local/bin/yq
fi
KUSTOM=k8s/overlays/oci-arm/kustomization.yaml
yq -i '.images = (.images // [])' "$KUSTOM"
for svc in ${{ steps.changes.outputs.kustomize_services }}; do
yq -i "(.images) |= map(select(.name != \"commerce/$svc\"))" "$KUSTOM"
yq -i "(.images) += [{\"name\": \"commerce/$svc\", \"newName\": \"$REGISTRY/$svc\", \"newTag\": \"$TAG\"}]" "$KUSTOM"
done
yq -i '(.images) |= sort_by(.name)' "$KUSTOM"
echo "── 갱신된 images: 항목 미리보기"
yq '.images[] | select(.newTag == "'"${TAG}"'") | "\(.name) → \(.newName):\(.newTag)"' "$KUSTOM"
# main branch push 일 때만 commit-back.
# tag push (v*) 는 detached HEAD 라 git push to main 불가 + 의미상 release
# 시점엔 manifest 갱신 별개로 처리.
- name: Commit manifest update
if: ${{ steps.changes.outputs.kustomize_services != '' && github.ref == 'refs/heads/main' }}
run: |
set -euo pipefail
if git diff --quiet k8s/overlays/oci-arm/kustomization.yaml; then
echo "No manifest changes — skip commit"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add k8s/overlays/oci-arm/kustomization.yaml
UPDATED_COUNT=$(echo "${{ steps.changes.outputs.kustomize_services }}" | tr ' ' '\n' | wc -l)
SVCS="${{ steps.changes.outputs.kustomize_services }}"
git commit -m "ci: bump ${UPDATED_COUNT} service image tag(s) to ${{ steps.meta.outputs.tag }} [skip ci]" -m "Services: ${SVCS}"
git push
- name: Summary
if: ${{ steps.changes.outputs.kustomize_services != '' }}
run: |
{
echo "## Image publish (OCIR / arm64) — incremental"
echo "Registry: \`${{ steps.meta.outputs.registry }}\`"
echo "Tag : \`${{ steps.meta.outputs.tag }}\`"
echo
echo "### Rebuilt"
echo "- JVM : \`${{ steps.changes.outputs.jvm_services }}\`"
echo "- Docker: \`${{ steps.changes.outputs.docker_services }}\`"
echo
echo "→ 변경된 서비스만 이미지 push + kustomization.yaml 갱신."
echo "→ Argo CD 가 변경된 deployment 만 rolling update."
} >> "$GITHUB_STEP_SUMMARY"