Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 107 additions & 10 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,50 @@

이 디렉토리는 CM-ANT 프로젝트의 GitHub Actions 워크플로우를 포함합니다.

## 배경 설명

### Docker Hub 다이제스트 관리 전략

Docker Hub에서 **하나의 태그에 여러 digest가 존재하는 것은 의도된 기능**입니다. 이는 멀티 플랫폼 지원, 이력 관리, 롤백 기능을 위한 정상적인 설계입니다.

**현재 상황**:
- Docker Hub의 `v0.4.0` 태그에 2개 이상의 digest 존재 (정상)
- `pull_policy: always` 설정에도 불구하고 이전 digest의 이미지가 pull될 수 있음
- Docker 클라이언트가 첫 번째 digest를 우선 선택하지만, 순서가 최신순이 아닐 수 있음

**해결 전략**:

#### 1. 기본 전략 (권장)
- **mayfly의 `--force` 옵션**: Docker Hub API로 digest 정보 확인 후 최신 digest로 명시적 pull
- **digest 기반 설치**: 태그 대신 digest로 설치하여 정확한 버전 보장
- **기존 호환성 유지**: 기본 동작은 기존과 동일하게 유지

**사용법**:
```bash
# 전체 서비스 업데이트 (digest 기반)
mayfly infra update --force

# 특정 서비스만 업데이트 (digest 기반)
mayfly infra update --force -s cm-ant

# 기존 방식 (기본 동작)
mayfly infra update -s cm-ant
```

**동작 방식**:
1. **docker-compose.yaml 파싱**: 실제 이미지 정보 추출
2. **Docker Hub API 호출**: 각 태그의 digest 목록 조회
3. **최신 digest 선택**: `last_pushed` 시간 기준으로 정렬
4. **digest 기반 pull**: `image@digest` 형태로 명시적 pull
5. **태그 재할당**: 원본 태그로 재태깅하여 호환성 보장

#### 2. 보조 전략 (현재 구현)
- **하나의 태그 = 하나의 digest** 원칙 적용
- 기존 Docker 이미지 삭제 후 새 이미지 생성
- 깔끔한 다이제스트 관리로 예측 가능한 동작 보장

**이러한 배경으로 Docker 이미지 삭제 및 재생성 기능이 보조 수단으로 제공됩니다.**

## 기존 워크플로우

### 1. Continuous Integration (CI)
Expand All @@ -18,7 +62,31 @@

## 새로운 워크플로우

### 3. Rebuild Docker Image
### 3. Delete Docker Image
- **파일**: `delete-docker-image.yaml`
- **트리거**: 수동 실행 (`workflow_dispatch`)
- **기능**: Docker Hub에서 특정 태그의 이미지를 삭제

#### 사용법:
1. GitHub 리포지토리의 **Actions** 탭으로 이동
2. **Delete Docker Image** 워크플로우 선택
3. **Run workflow** 버튼 클릭
4. 입력값 설정:
- `tag_name`: 삭제할 태그명 (예: `v0.4.0`, `0.4.0`)
- `confirm_delete`: `DELETE` 입력 (확인용)

#### 특징:
- ✅ **안전한 삭제**: 태그 존재 여부 확인 후 삭제
- ✅ **삭제 검증**: 삭제 후 검증 단계 포함
- ✅ **상세한 로그**: 삭제 과정의 모든 단계 표시
- ✅ **권한 처리**: Personal Access Token 우선 사용

#### 사용 시기:
- Docker Hub에서 잘못된 이미지가 업로드되었을 때
- 태그 충돌을 해결하고 싶을 때
- 수동으로 특정 태그를 정리하고 싶을 때

### 4. Rebuild Docker Image
- **파일**: `retag-release.yaml`
- **트리거**: 수동 실행 (`workflow_dispatch`)
- **기능**: 기존 Git 태그 위치는 그대로 유지하고 Docker 이미지만 재빌드
Expand All @@ -42,7 +110,7 @@
- Docker 이미지 빌드 과정에서 문제가 있었을 때
- Git 태그 위치는 유지하되 Docker 이미지만 새로 만들고 싶을 때

### 4. Move Tag to Latest Commit ⚠️
### 5. Move Tag to Latest Commit ⚠️
- **파일**: `force-rebuild.yaml`
- **트리거**: 수동 실행 (`workflow_dispatch`)
- **기능**: Git 태그를 현재 HEAD 커밋으로 이동하고 Docker 이미지 재빌드
Expand Down Expand Up @@ -105,9 +173,25 @@ git push origin v0.4.1

다음 Secrets이 설정되어 있어야 합니다:

### 기본 인증 정보
- `DOCKER_USERNAME`: Docker Hub 사용자명
- `DOCKER_PASSWORD`: Docker Hub 비밀번호 또는 액세스 토큰
- `DOCKER_PASSWORD`: Docker Hub 비밀번호 (이미지 생성용)

### Personal Access Tokens (PAT)
- `DOCKER_PAT`: Docker Hub Personal Access Token (이미지 삭제용)
- `CR_PAT`: GitHub Container Registry Personal Access Token
- `UPDATE_SWAGGER_DOC_PAT`: Swagger 문서 업데이트용 PAT
- `CB_GITHUB_ROBOT_PAT`: GitHub Robot용 PAT

### Docker Hub PAT 생성 방법
1. **Docker Hub 로그인** → **Account Settings** → **Security**
2. **"New Access Token"** 클릭
3. **권한 설정**: `Read, Write, Delete` 권한 부여
4. **토큰 생성** 후 GitHub Organization Secrets에 `DOCKER_PAT`로 추가

### 권한 차이
- **`DOCKER_PASSWORD`**: 이미지 생성/업데이트만 가능 (삭제 불가)
- **`DOCKER_PAT`**: 이미지 생성/업데이트/삭제 모두 가능

## 워크플로우 실행 권한

Expand All @@ -116,10 +200,12 @@ git push origin v0.4.1

## 워크플로우 선택 가이드

### 🔄 Rebuild Docker Image vs 🏷️ Move Tag to Latest Commit
### 🚀 mayfly --force vs 🗑️ Delete Docker Image vs 🔄 Rebuild Docker Image vs 🏷️ Move Tag to Latest Commit

| 상황 | 권장 워크플로우 | 이유 |
|------|----------------|------|
| 상황 | 권장 방법 | 이유 |
|------|-----------|------|
| **일반적인 최신 이미지 업데이트** | **`mayfly infra update --force`** | digest 기반으로 정확한 최신 버전 보장 |
| 잘못된 이미지 삭제 | **Delete Docker Image** | 특정 태그만 삭제, 안전함 |
| Docker Hub digest 충돌 | **Rebuild Docker Image** | Git 태그 위치 유지, 안전함 |
| Docker 이미지 빌드 문제 | **Rebuild Docker Image** | 같은 코드로 새 이미지 생성 |
| 최신 커밋을 태그에 반영 | **Move Tag to Latest Commit** | Git 태그 위치 변경 필요 |
Expand All @@ -138,14 +224,25 @@ git push origin v0.4.1
1. GitHub Secrets 설정 확인
2. 리포지토리 권한 확인
3. 입력값 형식 확인 (태그명은 `v0.4.0` 형식)
4. 확인 코드 정확히 입력 (`REBUILD` 또는 `MOVE_TAG`)
4. 확인 코드 정확히 입력 (`DELETE`, `REBUILD` 또는 `MOVE_TAG`)

### Docker Hub API 오류
1. Docker Hub 계정 권한 확인
2. API 레이트 리미트 확인
3. 네트워크 연결 상태 확인
1. **HTTP 401 오류**: `DOCKER_PAT` 설정 확인 (삭제 권한 필요)
2. **HTTP 403 오류**: Docker Hub 계정 권한 확인
3. **API 레이트 리미트**: Docker Hub API 사용량 확인
4. **네트워크 연결**: 연결 상태 및 방화벽 설정 확인

### Docker 이미지 삭제 관련 오류
1. **삭제 권한 부족**: `DOCKER_PAT` 토큰에 `Delete` 권한이 있는지 확인
2. **태그 존재 여부**: 삭제하려는 태그가 실제로 존재하는지 확인
3. **Organization 권한**: Docker Hub Organization의 관리자 권한 확인

### Git 태그 관련 오류
1. 기존 태그가 존재하는지 확인
2. 태그 삭제 권한 확인
3. 원격 저장소 접근 권한 확인

### 다이제스트 문제 해결
1. **여러 digest 확인**: `curl -s "https://hub.docker.com/v2/repositories/cloudbaristaorg/cm-ant/tags/0.4.0/" | jq '.images'`
2. **최신 digest 확인**: `jq '.images[0].digest'`로 첫 번째 digest 확인
3. **캐시 문제**: Docker Hub API 캐시로 인한 지연 (보통 10-30분)
21 changes: 16 additions & 5 deletions .github/workflows/delete-docker-image.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,22 @@ jobs:
echo "🗑️ Deleting Docker image: ${{ env.DOCKER_REGISTRY_NAME }}/${{ env.IMAGE_NAME }}:$TAG_NAME"

# Delete the Docker image from Docker Hub
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
-X DELETE \
-u ${{ secrets.DOCKER_USERNAME }}:${{ secrets.DOCKER_PASSWORD }} \
"https://hub.docker.com/v2/repositories/${{ env.DOCKER_REGISTRY_NAME }}/${{ env.IMAGE_NAME }}/tags/$TAG_NAME/" \
-H "Accept: application/json")
# Try with Personal Access Token first, fallback to password
if [ -n "${{ secrets.DOCKER_PAT }}" ]; then
echo "Using Personal Access Token for deletion"
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
-X DELETE \
-H "Authorization: Bearer ${{ secrets.DOCKER_PAT }}" \
"https://hub.docker.com/v2/repositories/${{ env.DOCKER_REGISTRY_NAME }}/${{ env.IMAGE_NAME }}/tags/$TAG_NAME/" \
-H "Accept: application/json")
else
echo "Using username/password for deletion"
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
-X DELETE \
-u ${{ secrets.DOCKER_USERNAME }}:${{ secrets.DOCKER_PASSWORD }} \
"https://hub.docker.com/v2/repositories/${{ env.DOCKER_REGISTRY_NAME }}/${{ env.IMAGE_NAME }}/tags/$TAG_NAME/" \
-H "Accept: application/json")
fi

if [ "$RESPONSE" = "204" ]; then
echo "✅ Docker image deleted successfully"
Expand Down
42 changes: 36 additions & 6 deletions .github/workflows/force-rebuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ on:
description: 'Tag name to move to latest commit (e.g., v0.4.0)'
required: true
type: string
delete_existing_docker_image:
description: 'Delete existing Docker Hub image before rebuilding?'
required: true
type: choice
default: 'yes'
options:
- 'yes'
- 'no'
confirm_move:
description: '⚠️ WARNING: This will move the Git tag to current HEAD and rebuild Docker image. Type "MOVE_TAG" to confirm'
required: true
Expand Down Expand Up @@ -56,20 +64,38 @@ jobs:

echo "Git tag $TAG_NAME deleted"

- name: Delete existing Docker Hub tag
- name: Delete existing Docker Hub tag (conditional)
if: github.event.inputs.delete_existing_docker_image == 'yes'
run: |
TAG_NAME="${{ github.event.inputs.tag_name }}"
echo "Deleting existing Docker Hub tag: $TAG_NAME"

# Delete the tag from Docker Hub
curl -X DELETE \
-u ${{ secrets.DOCKER_USERNAME }}:${{ secrets.DOCKER_PASSWORD }} \
"https://hub.docker.com/v2/repositories/${{ env.DOCKER_REGISTRY_NAME }}/${{ env.IMAGE_NAME }}/tags/$TAG_NAME/" \
-H "Accept: application/json" 2>/dev/null || echo "Docker Hub tag $TAG_NAME not found"
# Try with Personal Access Token first, fallback to password
if [ -n "${{ secrets.DOCKER_PAT }}" ]; then
echo "Using Personal Access Token for deletion"
curl -X DELETE \
-H "Authorization: Bearer ${{ secrets.DOCKER_PAT }}" \
"https://hub.docker.com/v2/repositories/${{ env.DOCKER_REGISTRY_NAME }}/${{ env.IMAGE_NAME }}/tags/$TAG_NAME/" \
-H "Accept: application/json" 2>/dev/null || echo "Docker Hub tag $TAG_NAME not found"
else
echo "Using username/password for deletion"
curl -X DELETE \
-u ${{ secrets.DOCKER_USERNAME }}:${{ secrets.DOCKER_PASSWORD }} \
"https://hub.docker.com/v2/repositories/${{ env.DOCKER_REGISTRY_NAME }}/${{ env.IMAGE_NAME }}/tags/$TAG_NAME/" \
-H "Accept: application/json" 2>/dev/null || echo "Docker Hub tag $TAG_NAME not found"
fi

echo "Docker Hub tag $TAG_NAME deleted"

- name: Wait for cleanup
- name: Skip Docker Hub deletion
if: github.event.inputs.delete_existing_docker_image == 'no'
run: |
echo "Skipping Docker Hub image deletion as requested by user"
echo "⚠️ Note: This may result in multiple digests for the same tag"

- name: Wait for cleanup (conditional)
if: github.event.inputs.delete_existing_docker_image == 'yes'
run: |
echo "Waiting 30 seconds for cleanup to complete..."
sleep 30
Expand Down Expand Up @@ -149,5 +175,9 @@ jobs:
echo "- **Tag**: ${{ github.event.inputs.tag_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **New Digest**: ${{ steps.docker_build.outputs.digest }}" >> $GITHUB_STEP_SUMMARY
echo "- **New Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
echo "- **Docker Image Deletion**: ${{ github.event.inputs.delete_existing_docker_image }}" >> $GITHUB_STEP_SUMMARY
echo "- **Status**: ✅ Successfully moved tag to latest commit" >> $GITHUB_STEP_SUMMARY
echo "- **⚠️ Warning**: Git tag position has been changed!" >> $GITHUB_STEP_SUMMARY
if [ "${{ github.event.inputs.delete_existing_docker_image }}" = "no" ]; then
echo "- **⚠️ Note**: Multiple digests may exist for the same tag" >> $GITHUB_STEP_SUMMARY
fi
44 changes: 37 additions & 7 deletions .github/workflows/retag-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ on:
description: 'Tag name to rebuild Docker image (e.g., v0.4.0)'
required: true
type: string
delete_existing_docker_image:
description: 'Delete existing Docker Hub image before rebuilding?'
required: true
type: choice
default: 'yes'
options:
- 'yes'
- 'no'
confirm_rebuild:
description: 'Confirm Docker image rebuild (type "REBUILD" to confirm)'
required: true
Expand Down Expand Up @@ -42,20 +50,38 @@ jobs:
fi
echo "Validating tag: $TAG_NAME"

- name: Delete existing Docker image from Docker Hub
- name: Delete existing Docker image from Docker Hub (conditional)
if: github.event.inputs.delete_existing_docker_image == 'yes'
run: |
TAG_NAME="${{ github.event.inputs.tag_name }}"
echo "Deleting existing Docker image for tag: $TAG_NAME"

# Delete the Docker image from Docker Hub (Git tag position remains unchanged)
curl -X DELETE \
-u ${{ secrets.DOCKER_USERNAME }}:${{ secrets.DOCKER_PASSWORD }} \
"https://hub.docker.com/v2/repositories/${{ env.DOCKER_REGISTRY_NAME }}/${{ env.IMAGE_NAME }}/tags/$TAG_NAME/" \
-H "Accept: application/json"
# Delete the Docker image from Docker Hub (Git tag position remains unchanged)
# Try with Personal Access Token first, fallback to password
if [ -n "${{ secrets.DOCKER_PAT }}" ]; then
echo "Using Personal Access Token for deletion"
curl -X DELETE \
-H "Authorization: Bearer ${{ secrets.DOCKER_PAT }}" \
"https://hub.docker.com/v2/repositories/${{ env.DOCKER_REGISTRY_NAME }}/${{ env.IMAGE_NAME }}/tags/$TAG_NAME/" \
-H "Accept: application/json"
else
echo "Using username/password for deletion"
curl -X DELETE \
-u ${{ secrets.DOCKER_USERNAME }}:${{ secrets.DOCKER_PASSWORD }} \
"https://hub.docker.com/v2/repositories/${{ env.DOCKER_REGISTRY_NAME }}/${{ env.IMAGE_NAME }}/tags/$TAG_NAME/" \
-H "Accept: application/json"
fi

echo "Docker image for tag $TAG_NAME deleted from Docker Hub"

- name: Wait for tag deletion
- name: Skip Docker Hub deletion
if: github.event.inputs.delete_existing_docker_image == 'no'
run: |
echo "Skipping Docker Hub image deletion as requested by user"
echo "⚠️ Note: This may result in multiple digests for the same tag"

- name: Wait for tag deletion (conditional)
if: github.event.inputs.delete_existing_docker_image == 'yes'
run: |
echo "Waiting 30 seconds for Docker Hub to process deletion..."
sleep 30
Expand Down Expand Up @@ -124,4 +150,8 @@ jobs:
echo "- **Tag**: ${{ github.event.inputs.tag_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **New Digest**: ${{ steps.docker_build.outputs.digest }}" >> $GITHUB_STEP_SUMMARY
echo "- **Git Tag Position**: Unchanged (same commit)" >> $GITHUB_STEP_SUMMARY
echo "- **Docker Image Deletion**: ${{ github.event.inputs.delete_existing_docker_image }}" >> $GITHUB_STEP_SUMMARY
echo "- **Status**: ✅ Successfully rebuilt Docker image" >> $GITHUB_STEP_SUMMARY
if [ "${{ github.event.inputs.delete_existing_docker_image }}" = "no" ]; then
echo "- **⚠️ Note**: Multiple digests may exist for the same tag" >> $GITHUB_STEP_SUMMARY
fi