사용자 기여 매핑 개발자 문서
사용자 기여 매핑은 가져오기 전에 소스에 있는 사용자가 공개 이메일로 프로비저닝되어 있지 않아도 가져온 레코드를 사용자에게 귀속시킬 수 있는 기능입니다. 사용자 기여 매핑은 마이그레이션 중에 플레이스홀더 사용자에게 기여를 할당하기 위해 각 임포터 내에서 구현되지만, 이 매핑을 사용하는 모든 임포터에 동일한 프로세스가 적용됩니다.
사용자 기여 매핑은 가져오기 전에 소스에 있는 사용자가 공개 이메일로 프로비저닝되어 있지 않아도 가져온 레코드를 사용자에게 귀속시킬 수 있는 기능입니다. 대신, 가져오기가 완료된 후 해당 기여에 실제 사용자를 할당할 수 있을 때까지 가져온 레코드에서 자리 표시자 역할을 하는 더미 User 레코드가 생성됩니다.
사용자 기여 매핑은 마이그레이션 중에 플레이스홀더 사용자에게 기여를 할당하기 위해 각 임포터 내에서 구현되지만, 이 매핑을 사용하는 모든 임포터에 동일한 프로세스가 적용됩니다. 그룹 소유자가 플레이스홀더 사용자에게 실제 사용자를 재할당하는 프로세스는 마이그레이션 완료 후에 이루어지며 마이그레이션과는 별개입니다.
용어 및 관련 모델 용어 사전#
| 용어 | 해당 ActiveRecord 모델 |
정의 |
|---|---|---|
| 소스 사용자 | Import::SourceUser |
플레이스홀더 사용자를 실제 사용자에 매핑하고 재할당 세부 정보, 임포트 소스, 최상위 그룹 연결을 추적합니다. |
| 플레이스홀더 사용자 | User with user_type: 'placeholder' |
임포트 후 실제 사용자로 재할당되기 위한 마이그레이션 중 외래 키 제약 조건을 충족하는 User 레코드입니다. 플레이스홀더 사용자는 로그인할 수 없으며 GitLab에서 권한이 없습니다. |
| 담당자 사용자, 실제 사용자 | User with user_type: 'human' |
플레이스홀더 사용자에게 할당된 실제 사용자입니다. |
| 사용자 기여 | 모든 GitLab ActiveRecord 모델 | 마이그레이션 중에 가져온 User에 속하는 모든 ActiveRecord 모델. 예: 머지 리퀘스트 담당자, 노트, 멤버십 등. |
| 플레이스홀더 참조 | Import::SourceUserPlaceholderReference |
멤버십을 제외한 데이터베이스 전반의 모든 플레이스홀더 사용자 기여를 추적하는 별도의 모델입니다. |
| 플레이스홀더 멤버십 | Import::Placeholders::Membership |
플레이스홀더 사용자에 속하는 가져온 멤버십을 추적하는 별도의 모델입니다. Member 레코드는 플레이스홀더가 멤버로 나타나는 것을 방지하기 위해 마이그레이션 중에 플레이스홀더 사용자를 위해 생성되지 않습니다. |
| 임포트 사용자 | Import::NamespaceImportUser |
레코드를 일반 플레이스홀더에 할당할 수 없을 때 사용되는 플레이스홀더 사용자입니다. 예: 플레이스홀더 사용자 제한에 도달했을 때. |
| 플레이스홀더 세부 정보 | Import::PlaceholderUserDetail |
최상위 그룹이 삭제될 때 플레이스홀더 사용자를 삭제할 수 있도록 어떤 네임스페이스에 플레이스홀더 사용자가 있는지 추적하는 레코드입니다. |
| 플레이스홀더 사용자 테이블 | N/A | 그룹 소유자가 UI에서 플레이스홀더 사용자에게 할당할 실제 사용자를 선택할 수 있는 플레이스홀더 사용자 테이블. 최상위 그룹의 멤버 페이지의 플레이스홀더 탭에 위치. 그룹 소유자만 볼 수 있음. |
임포트 중 플레이스홀더 사용자 생성#
플레이스홀더 사용자를 실제 사용자로 재할당하기 전에 임포트 중에 플레이스홀더 사용자를 생성해야 합니다.
오류 처리#
SourceUserMapper는 소스 사용자 또는 플레이스홀더 사용자 생성을 재시도해야 함을 나타내는 두 가지 오류를 발생시킬 수 있습니다:
FailedToObtainLockError: 소스 사용자 생성을 위한 잠금을 얻을 수 없을 때 발생DuplicatedUserError: 중복 소스 사용자가 감지될 때 발생
가져온 기여에 대한 사용자 매핑 할당 플로우#
소스 코드 보기
flowchart
%%% nodes
Start{{
Group or project
migration is started
}}
FetchInfo[
Fetch information
about the contribution
]
FetchUserInfo[
Fetch information about
the user who is associated
with the contribution.
]
CheckSourceUser{
Has a user in the destination
instance already accepted being
mapped to the source user?
}
AssignToUser[
Assign contribution to the user
]
PlaceholderLimit{
Namespace reached
the placeholder limit?
}
CreatePlaceholderUser[
Create a placeholder user and
save the details of the source user
]
AssignContributionPlaceholder[
Assign contribution
to placeholder user
]
AssignImportUser[
Assign contributions to the
ImportUser and save source user
details
]
ImportContribution[
Save contribution
into the database
]
PushPlaceholderReference[
Push instance of placeholder
reference to Redis
]
LoadPlaceholderReference(((
Load placeholder references
into the database
)))
%%% connections
Start --> FetchInfo
FetchInfo --> FetchUserInfo
FetchUserInfo --> CheckSourceUser
CheckSourceUser -- Yes --> AssignToUser
CheckSourceUser -- No --> PlaceholderLimit
PlaceholderLimit -- No --> CreatePlaceholderUser
CreatePlaceholderUser --> AssignContributionPlaceholder
PlaceholderLimit -- Yes --> AssignImportUser
AssignToUser-->ImportContribution
AssignContributionPlaceholder-->ImportContribution
AssignImportUser-->ImportContribution
ImportContribution-->PushPlaceholderReference
PushPlaceholderReference-->LoadPlaceholderReference</code></pre></details></div>
임포터에서 사용자 매핑을 구현하는 방법#
-
임포터 내에서 사용자 매핑에 대한 기능 플래그를 구현합니다.
-
임포트 중에 플래그 상태를 변경하더라도 진행 중인 임포트에 영향을 미치지 않도록 임포터 시작 시 기능 플래그 상태가 저장되는지 확인합니다.
- 서드파티 임포터는 임포트 시작 시
project.import_data[:data][:user_contribution_mapping_enabled]를 설정하여 이를 수행합니다. 예시로 Gitlab::GithubImport::Settings 또는 Gitlab::BitbucketServerImport::ProjectCreator를 참조하십시오.
- 직접 전송은
BulkImports::CreateService의 EphemeralData를 사용하여 기능 플래그 상태를 캐시합니다.
-
데이터베이스에 사용자 기여를 저장하기 전에 Gitlab::Import::SourceUserMapper#find_or_create_source_user를 사용하여 기여를 귀속할 올바른 User를 찾습니다.
-
매핑된 사용자와 함께 기여를 데이터베이스에 저장합니다.
-
레코드가 지속되면 Import::PlaceholderReferences::PushService를 초기화하고 실행하여 초기화된 Import::SourceUserPlaceholderReference를 Redis에 푸시합니다. from_record를 사용하여 서비스를 초기화하는 것이 가장 편리한 경우가 많습니다.
-
LoadPlaceholderReferencesWorker를 사용하여 캐시된 Import::SourceUserPlaceholderReference를 비동기적으로 지속합니다. 이 워커는 Import::PlaceholderReferences::LoadService를 사용하여 플레이스홀더 참조를 지속합니다. 스테이지 끝 및 임포트 끝에 주기적으로 이 워커를 호출하는 것이 좋습니다.
[!note]
플레이스홀더 사용자 참조는 import_source_user_placeholder_references 테이블에 대한 동시 쓰기가 너무 많아지는 것을 방지하기 위해 로드하기 전에 캐시됩니다. 데이터베이스 레코드가 플레이스홀더 사용자의 ID를 참조하지만 어떤 이유로든 플레이스홀더 참조가 지속되지 않으면 기여를 재할당할 수 없으며 플레이스홀더 사용자가 삭제되지 않을 수 있습니다.
-
캐시된 모든 플레이스홀더 참조가 로드될 때까지 임포트 완료를 지연합니다.
- 병렬 서드파티 임포터는 프로젝트에 대한 Redis에 남은 플레이스홀더 참조가 있으면
FinishImportWorker를 다시 큐에 넣어 이를 수행합니다. 예시로 Gitlab::GithubImport::Stage::FinishImportWorker를 참조하십시오.
- 동기식 서드파티 임포터는 다른 방식으로 플레이스홀더 참조 로드 완료를 기다려야 합니다. 예를 들어 Gitea 임포터는 플레이스홀더 사용자 참조가 로드될 때까지 임포트를 지연시키기 위해
Kernel.sleep를 사용합니다.
- 직접 전송은
BulkImport::ProcessService를 사용하여 BulkImportWorker를 다시 큐에 넣는 병렬 임포터와 유사하게 작동하여 플레이스홀더 사용자 로드가 완료되지 않으면 마이그레이션 완료를 지연합니다.
임포트 후 플레이스홀더 사용자 재할당#
플레이스홀더 사용자 재할당은 임포트가 완료된 후에 이루어집니다. 재할당 프로세스는 플레이스홀더를 생성한 임포트 유형에 관계없이 모든 플레이스홀더 사용자에 대해 동일합니다.
재할당 플로우#
Mermaid 다이어그램 (51줄)소스 코드 보기
flowchart TD
%% Nodes
OwnerAssigns{{
Group owner requests a
source user to be assigned
to a user in the destination
}}
Notification[
Notification is sent
to the user
]
ReceivesNotification[
User receives the notification and
clicks the button to see more details
]
ClickMoreDetails[
The user is redirected to the more details page
]
CheckRequestStatus{
Group owner has canceled
the request?
}
RequestCanceledPage(((
Shows request canceled
by the owner message
)))
OwnerCancel(
Group owner chooses to cancel
the assignment request
)
ReassigmentOptions{
Show reassignment options
}
ReassigmentStarts(((
Start the reassignment
of the contributions
)))
ReassigmentRejected(((
Shows request rejected
by the user message
)))
%% Edge connections between nodes
OwnerAssigns --> Notification --> ReceivesNotification --> ClickMoreDetails
OwnerAssigns --> OwnerCancel
ClickMoreDetails --> CheckRequestStatus
CheckRequestStatus -- Yes --> RequestCanceledPage
CheckRequestStatus -- No --> ReassigmentOptions
ReassigmentOptions -- User accepts --> ReassigmentStarts
ReassigmentOptions -- User rejects --> ReassigmentRejected
OwnerCancel-.->CheckRequestStatus
재할당 플로우의 각 단계는 소스 사용자 상태에 해당합니다:
Mermaid 다이어그램 (13줄)소스 코드 보기
stateDiagram-v2
[*] --> pending_reassignment
pending_reassignment --> reassignment_in_progress: Reassign user and bypass assignee confirmation
awaiting_approval --> reassignment_in_progress: Accept reassignment
reassignment_in_progress --> completed: Contribution reassignment completed successfully
reassignment_in_progress --> failed: Error reassigning contributions
pending_reassignment --> awaiting_approval: Reassign user
awaiting_approval --> pending_reassignment: Cancel reassignment
awaiting_approval --> rejected: Reject reassignment
rejected --> pending_reassignment: Cancel reassignment
rejected --> keep_as_placeholder: Keep as placeholder
pending_reassignment --> keep_as_placeholder: Keeps as placeholder
소스 사용자에 직접 state_machines-activerecord 메서드를 호출하는 대신 각 상태 전환에 대한 서비스가 구현되어 유효성 검사 및 동작을 일관되게 처리합니다:
Import::SourceUsers::ReassignService: 실제 사용자로의 재할당을 시작합니다. 플레이스홀더 확인 우회가 활성화된 경우 사용자 기여 재할당을 시작할 수 있습니다.
Import::SourceUsers::AcceptReassignmentService: 재할당에 대한 사용자 수락을 처리하고 사용자 기여 재할당을 시작합니다.
Import::SourceUsers::RejectReassignmentService: 재할당에 대한 사용자 거부를 처리합니다.
Import::SourceUsers::CancelReassignmentService: 사용자가 수락하기 전 또는 거부한 후에 보류 중인 재할당 요청을 취소합니다.
Import::SourceUsers::KeepAsPlaceholderService: 단일 사용자를 플레이스홀더로 유지하도록 표시합니다.
대량 요청 및 기타 관련 동작을 처리하기 위한 몇 가지 추가 서비스가 있습니다:
Import::SourceUsers::GenerateCsvService: 플레이스홀더 사용자를 대량으로 재할당하기 위한 CSV 파일을 생성합니다.
Import::SourceUsers::BulkReassignFromCsvService: CSV 파일에서 대량 재할당을 처리하며, 궁극적으로 CSV의 각 플레이스홀더 사용자에 대해 Import::SourceUsers::ReassignService를 호출합니다.
Import::SourceUsers::KeepAllAsPlaceholderService: 할당되지 않은 모든 플레이스홀더 사용자를 무기한 플레이스홀더 사용자로 유지하도록 표시합니다.
Import::SourceUsers::ResendNotificationService: 재할당된 사용자에게 재할당 알림 이메일을 다시 보냅니다.
플레이스홀더 사용자 우회가 활성화된 재할당 플로우#
플레이스홀더 확인 우회가 활성화되면 실제 사용자의 확인 없이 재할당 시 즉시 사용자 기여 재할당이 시작됩니다.
Mermaid 다이어그램 (22줄)소스 코드 보기
flowchart LR
%% Nodes
OwnerAssigns{{
Administrator or enterprise group owner
requests a source user to be assigned
to a user in the destination
}}
ConfirmAssignmentWithBypass[
Group owner confirms assignment
without assignee confirmation
]
ReassigmentStarts((
Start the reassignment
of the contributions
))
NotifyAssignee(((
Reassigned real user
notified that contributions
have been reassigned
)))
%% Edge connections between nodes
OwnerAssigns --> ConfirmAssignmentWithBypass --> ReassigmentStarts --> NotifyAssignee
담당자의 확인을 우회하는 것은 다음 상황에서만 가능합니다:
- 관리자가
allow_bypass_placeholder_confirmation 애플리케이션 설정이 활성화된 GitLab Self-Managed 인스턴스에서 플레이스홀더 사용자를 재할당합니다. Import::UserMapping::AdminBypassAuthorizer 모듈이 모든 기준을 충족하는지 결정합니다.
- GitLab Premium 또는 Ultimate를 사용하는 엔터프라이즈 그룹 소유자가
allow_enterprise_bypass_placeholder_confirmation 그룹 설정이 활성화된 .com에서 엔터프라이즈 내의 실제 사용자로 플레이스홀더 사용자를 재할당합니다. Import::UserMappingEnterpriseBypassAuthorizer 모듈이 모든 기준을 충족하는지 결정합니다.
사용자 기여 재할당#
실제 사용자가 재할당을 수락하면 플레이스홀더 사용자 ID에 대한 모든 외래 키 참조를 재할당된 사용자의 ID로 교체하는 프로세스가 시작됩니다:
-
Import::SourceUsers::AcceptReassignmentService가 비동기적으로 Import::ReassignPlaceholderUserRecordsWorker를 호출합니다.
-
워커는 Import::ReassignPlaceholderUserRecordsService를 실행하여 소스 사용자에 속하는 플레이스홀더 참조와 플레이스홀더 멤버십을 쿼리하여 플레이스홀더 사용자 ID에 대한 외래 키 참조가 있는 레코드를 재할당자의 사용자 ID로 교체합니다.
-
서비스는 성공적인 재할당 후 플레이스홀더 참조와 플레이스홀더 멤버십을 삭제합니다.
-
서비스는 소스 사용자의 상태를 complete로 설정합니다.
[!note]
- 플레이스홀더 참조를 재할당할 수 없는 유효한 시나리오가 있습니다. 예를 들어 플레이스홀더 사용자 리뷰어와 함께 머지 리퀘스트에 리뷰어로 사용자가 추가된 다음, 이미 리뷰어였던 플레이스홀더에 재할당을 수락하는 경우. 이는 기여 재할당 중에
ActiveRecord::RecordNotUnique 오류를 발생시키지만 유효한 시나리오입니다.
- 처리되지 않은 오류로 인해 재할당이 실패할 가능성이 있습니다. 재할당은 항상 성공해야 하므로 이 문제를 조사해야 합니다.
-
서비스가 완료되면 워커는 플레이스홀더 사용자를 삭제하기 위해 비동기적으로 Import::DeletePlaceholderUserWorker를 호출합니다. 플레이스홀더 사용자 ID가 가져온 테이블에 여전히 참조되어 있으면 삭제되지 않습니다. 예외에 대해서는 AliasResolver의 columns_ignored_on_deletion을 확인하십시오.
-
재할당된 사용자의 확인 없이 플레이스홀더 사용자가 재할당된 경우, 재할당된 사용자에게 기여가 재할당되었음을 알리는 이메일이 전송됩니다.
플레이스홀더 참조 별칭 지정#
모델 이름과 열 이름을 import_source_user_placeholder_references 테이블에 저장하는 것은 취약합니다. 실제 모델 및 열 이름이 변경될 수 있으며 현재 이전 이름을 저장하는 플레이스홀더 레코드를 업데이트할 방법이 없습니다. 플레이스홀더 참조의 model, numeric_key, composite_key를 실제 이름으로 취급하는 대신 별칭으로 취급해야 합니다. Import::PlaceholderReferences::AliasResolver는 이러한 속성에 저장된 값을 실제 모델 및 열 이름에 매핑하는 데 사용됩니다.
Import::PlaceholderReferences::AliasResolver를 언제 업데이트해야 하는가?#
MissingAlias 오류로 인해 여기로 안내된 경우 Import::PlaceholderReferences::AliasResolver를 누락된 별칭으로 업데이트해야 합니다.
이 변경을 선제적으로 수행하는 경우 다음 조건에 해당할 때만 Import::PlaceholderReferences::AliasResolver를 업데이트해야 합니다:
- 작업 중인 모델이 사용자 기여 매핑을 구현하는 임포터 중 하나 이상에 의해 가져옵니다.
- 모델의 열이
User ID를 참조합니다. 즉, 열이 users(id)를 참조하는 외래 키 제약 조건이거나 열이 모델과 User 간의 연결을 설정합니다.
스키마 변경 시 Import::PlaceholderReferences::AliasResolver를 업데이트하는 방법#
가져온 모델이 변경되면 Import::PlaceholderReferences::AliasResolver를 업데이트해야 하는 몇 가지 시나리오가 있습니다.
새로운 사용자 참조 열이 추가되어 가져옵니다#
모델 별칭의 최신 버전에 새 열 키를 추가합니다. 새 열에 플레이스홀더 사용자에 속할 수 있는 사용자 ID가 채워지지 않는 한 이전 버전에 새 열을 추가할 필요가 없습니다.
예시:
User ID에 대한 참조인 last_updated_by_id가 snippets 테이블에 추가됩니다. author_id는 여전히 존재하며 변경되지 않았습니다.
"Snippet" => {
1 => {
model: Snippet,
- columns: { "author_id" => "author_id" }
+ columns: { "author_id" => "author_id", "last_updated_by_id" => "last_updated_by_id" }
}
},
사용자 참조 열이 이름 변경됩니다#
업데이트된 열 이름을 사용하여 모델 별칭에 새 버전을 추가합니다. 아직 재할당되지 않은 플레이스홀더 참조가 올바른 최신 열 이름을 업데이트하도록 모든 이전 버전의 열 값도 업데이트된 열 이름으로 업데이트합니다.
예시:
author_id가 snippets 테이블에서 user_id로 이름 변경됩니다. columns의 키는 동일하게 유지됩니다:
"Snippet" => {
1 => {
model: Snippet,
- columns: { "author_id" => "author_id" }
+ columns: { "author_id" => "user_id" }
+ },
+ 2 => {
+ model: Snippet,
+ columns: { "user_id" => "user_id" }
}
},
잠시 후 snippets 테이블의 user_id가 다시 created_by_id로 변경됩니다:
"Snippet" => {
1 => {
model: Snippet,
- columns: { "author_id" => "user_id" }
+ columns: { "author_id" => "created_by_id" }
},
2 => {
model: Snippet,
- columns: { "user_id" => "user_id" }
+ columns: { "user_id" => "created_by_id" }
+ },
+ 3 => {
+ model: Snippet,
+ columns: { "created_by_id" => "created_by_id" }
}
},
사용자 기여를 가진 새 모델이 가져옵니다#
새로 가져온 모델에 대해 ALIASES 해시에 별칭을 추가합니다. 모델이 GitLab에서 반드시 새 것일 필요는 없으며, 사용자 기여 매핑을 구현하는 임포터 중 하나 이상에 의해 새로 가져오기만 하면 됩니다.
예시:
직접 전송이 Todo를 가져오도록 업데이트되었습니다. Todo에는 user_id와 author_id의 두 가지 사용자 참조 열이 있습니다:
},
+"Todo" => {
+ 1 => {
+ model: Todo,
+ columns: { "user_id" => "user_id", "author_id" => "author_id"}
+ }
+},
"Vulnerability" => {
사용자 기여가 있는 모델의 이름이 변경됩니다#
이름 변경된 모델이 새로 가져온 모델인 것처럼 ALIASES 해시에 별칭을 추가합니다. 또한 모든 이전 버전의 모델 값을 새 모델 이름으로 업데이트합니다. 이전 이름으로 된 모델이 더 이상 존재하지 않더라도 이전 별칭을 제거하지 마십시오. 이전 모델 이름을 참조하는 사용되지 않은 플레이스홀더 참조가 여전히 존재할 수 있습니다.
예시:
Snippet의 이름이 Sample로 변경됩니다:
+"Sample" => {
+ 1 => {
+ model: Sample,
+ columns: { "author_id" => "author_id" }
+ }
+},
"Snippet" => {
1 => {
- model: Snippet,
+ model: Sample,
columns: { "author_id" => "author_id" }
}
},
에지 케이스 예시:
Snippet의 이름이 Sample로 변경된 후, Snippet 모델이 완전히 다른 용도로 다시 도입되어 가져옵니다. 새 Snippet 모델은 user_id를 사용하여 User에 속합니다. 이 경우 alias_version 1의 플레이스홀더 참조가 실제로 Sample에 대한 참조이므로 "Snippet" 별칭의 이전 버전을 업데이트하지 마십시오:
"Sample" => {
1 => {
model: Sample,
columns: { "author_id" => "author_id" }
}
},
"Snippet" => {
1 => {
model: Sample,
columns: { "author_id" => "author_id" }
},
+ 2 => {
+ model: Snippet,
+ columns: { "user_id" => "user_id" }
}
},
스펙#
Import::PlaceholderReferences::AliasResolver의 별칭 구성 변경은 일반적으로 스펙 파일에서 개별적으로 테스트할 필요가 없습니다. 스펙은 각 별칭 버전이 존재하는 열을 가리키고, 인덱싱되지 않은 열이 성능 문제를 방지하기 위해 columns_ignored_on_deletion에 나열되어 있는지 확인하도록 설정되어 있습니다.
