본문으로 건너뛰기

Git 그룹 namespace 동기화

Epic #1081 M2 에서 추가된 Keycloak Group → Gitea Organization 자동 동기화 + 그룹 namespace repo 생성 워크플로우를 정리합니다.

본 문서는 Git Service Operator Guide 의 M2 절을 보완하는 그룹 정책 진실의 원천입니다. 사용자/그룹 repo 분기 정책, namespace 컨벤션, M3 까지의 cutover 로드맵을 한 곳에서 추적합니다.

왜 그룹 namespace 가 필요한가

M1 (PR #1118) 에서 POST /api/v1/git/reposgroup_id 가 있으면 라우터 안에서 g-<group_id-short> 형태의 즉석 슬러그로 Gitea organization 을 생성했습니다. 이는 세 가지 문제를 만들었습니다:

  1. 역추적 불가능gitea_org_loginkeycloak_group_id 매핑이 DB 에 없어 멤버십 reconciliation 이 작동하지 않음.
  2. UI URL 노이즈https://gend.genon.ai/git/g-1a2b3c4d/foo 형태로 사람이 읽기 어려움. 그룹명 변경시 슬러그가 정렬되지 않음.
  3. 멤버십 cache 부재 — admin 이 "이 그룹의 Gitea 권한 누가 갖고 있나" 를 추적할 단일 테이블이 없음.

M2 는 이를 gitea_org_sync 테이블 + 3 endpoint 로 정리합니다.

데이터 모델

  • gitea_org_sync 는 M2 신규 테이블, gitea_repo_binding 은 M1 기존 테이블을 M2 에서 활성화한 것이다.

  • gitea_repo_bindingowner_id / group_idXOR check 제약이다 — 둘 중 정확히 하나만 채워진다.

  • gitea_org_sync.keycloak_group_id UNIQUE → 한 Keycloak 그룹당 한 Gitea org.

  • gitea_org_sync.gitea_org_id UNIQUE → 한 Gitea org 당 한 Keycloak 그룹 (역방향도 1:1, polymorphic 매핑 회피).

  • gitea_org_sync.gitea_org_login UNIQUE → Gitea side 의 슬러그도 구조적 유일성. DB 레벨 충돌은 IntegrityError → race 분기에서 winner 행 재읽기 (GitProvisionService.provision_user 와 동일 패턴).

API 표면

3 endpoint 가 추가됩니다. 전수 시그니처는 Git Service Operator Guide §M2 참조.

POST /api/v1/git/orgs/sync (admin)

Keycloak 그룹 1건을 Gitea organization 으로 idempotent sync 합니다.

curl -fsS -X POST "$API/api/v1/git/orgs/sync" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"keycloak_group_id": "11111111-2222-3333-4444-555555555555",
"keycloak_group_name": "Team Alpha"
}'
  • 첫 호출: Gitea GET /api/v1/orgs/team-alpha (404) → POST /api/v1/orgs (created) → INSERT gitea_org_sync.
  • 두 번째 호출: fast-path 로 동일 행 반환 (Gitea 미호출).
  • Gitea 에 이미 org 가 존재하는 경우 (operator 가 수동 생성): adopt 모드 로 INSERT gitea_org_sync (gitea_org_id 는 Gitea 응답에서 가져옴).

POST /api/v1/git/orgs/{org_login}/repos (member or admin)

그룹 namespace 아래 repo 생성. 사용자/그룹 분기를 라우터 안에 흩어두지 않고 별도 endpoint 로 분리해 ACL 점검을 단순화합니다.

# 그룹 멤버 토큰 — token claim 의 groups 에 keycloak_group_id 가 있어야 통과
curl -fsS -X POST "$API/api/v1/git/orgs/team-alpha/repos" \
-H "Authorization: Bearer $MEMBER_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name": "notebooks", "description": "Team Alpha shared notebooks"}'

응답: GiteaRepoResponse (full_name="team-alpha/notebooks", visibility="group", owner_id=null, group_id=<keycloak_group_id>, clone_url=...).

404 조건: gitea_org_sync 에 binding 없음 → admin 이 먼저 /orgs/sync 호출 필요. 403 조건: non-admin 사용자의 token claim groupskeycloak_group_id 없음.

POST /api/v1/git/orgs/membership/sync (admin)

사용자별 Gitea team 멤버십을 Keycloak 그룹 리스트와 reconcile.

curl -fsS -X POST "$API/api/v1/git/orgs/membership/sync" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"keycloak_sub": "user-sub-uuid",
"group_names": ["Team Alpha", "Team Beta"]
}'

응답:

{ "added": ["team-alpha"], "skipped": ["Team Beta"] }
  • added: 멤버십 PUT 이 200/204/422-already 로 처리된 Gitea org 슬러그.
  • skipped: gitea_org_sync binding 없는 Keycloak 그룹명 — admin 이 /orgs/sync 먼저 호출 필요.

M2 는 additive only. Keycloak 그룹에서 제거된 멤버를 Gitea 에서도 revoke 하는 경로는 M3 입니다 (Gitea "내 org 목록" API wrapper 필요). 이 한계 때문에 본 endpoint 의 응답은 removed: [] 필드를 노출하지 않습니다 — API contract 가 M3 에서 확장될 때 silent breaking 을 피하기 위해서.

슬러그 컨벤션

gend_api.services.git.group_sync._slugify_org_login 가 단일 진실의 원천:

입력출력
Team Alphateam-alpha
Data Science Groupdata-science-group
research-mlresearch-ml
한글 그룹group-<hex8> (한글은 ASCII 대체 후 빈 슬러그 → fallback)
(200 자)첫 40 자만 (Gitea org login max)

규칙:

  • 소문자화 → [a-z0-9._-] 외 문자는 - 로 치환 → 양끝 -._ 트림.
  • 빈 슬러그는 group-<token_hex(4)> 로 fallback (동일 그룹은 매 호출마다 새 슬러그가 나오므로 의도적으로 호출 1회만 발생하도록 idempotent fast-path 가 보호).
  • Gitea org login max 40 char. 더 긴 입력은 40 자에서 잘림.

Audit event

gend_api.services.git.git_audit.GIT_AUDIT_EVENTS 에 3개 신규 이벤트가 추가됩니다:

EventActor설명
git.org.synckeycloak_group_idadmin 이 /orgs/sync 로 새 org binding 생성 (fast-path 호출은 audit 미발생).
git.org.membershipkeycloak_sub멤버십 reconcile 가 add/skip 1건 이상 처리 (no-op 호출은 audit 미발생).
git.org.repo.createactor_id그룹 namespace 아래 repo 신규 생성 (full_name + org_login + group_id 기록).

OpenSearch gend-audit-* 인덱스에서 그룹별 활동을 추적할 때 위 3 이벤트로 필터하면 됩니다 (category=git AND event=git.org.*).

M3 로드맵

항목트리거비고
Keycloak event listener그룹 create / 멤버십 변경 → /orgs/sync + /membership/sync 자동 호출keycloak.events.AdminEventListenerProvider 구현 + Helm Chart 배포
Membership removalKeycloak 그룹에서 제거된 멤버 → Gitea team revokeGiteaClient.list_user_orgs wrap + 차집합 계산
Pre-receive hookforce-push 차단 + repo size 제한Gitea app.ini[git.config] + 별도 hook script
Hybrid profile사용자 + 그룹 dual namespace 노출 (UI 트리)UI sidebar 그룹 트리 + /api/v1/git/orgs/{org}/repos GET endpoint

관련 PR / 이슈

  • Epic #1081 — Notebook ↔ Git 형상관리
  • PR #1118 — M1 Step 3 (4 테이블 + 8 endpoint + JupyterHub bootstrap)
  • 본 PR — M2 (group namespace 동기화 + 멤버십 reconciler)