InfoGrab Docs

사용자 기여 매핑 개발자 문서

요약

사용자 기여 매핑은 가져오기 전에 소스에 있는 사용자가 공개 이메일로 프로비저닝되어 있지 않아도 가져온 레코드를 사용자에게 귀속시킬 수 있는 기능입니다. 사용자 기여 매핑은 마이그레이션 중에 플레이스홀더 사용자에게 기여를 할당하기 위해 각 임포터 내에서 구현되지만, 이 매핑을 사용하는 모든 임포터에 동일한 프로세스가 적용됩니다.

사용자 기여 매핑은 가져오기 전에 소스에 있는 사용자가 공개 이메일로 프로비저닝되어 있지 않아도 가져온 레코드를 사용자에게 귀속시킬 수 있는 기능입니다. 대신, 가져오기가 완료된 후 해당 기여에 실제 사용자를 할당할 수 있을 때까지 가져온 레코드에서 자리 표시자 역할을 하는 더미 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: 중복 소스 사용자가 감지될 때 발생

가져온 기여에 대한 사용자 매핑 할당 플로우#

Mermaid 다이어그램 (78줄)
소스 코드 보기
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--&gt;LoadPlaceholderReference</code></pre></details></div>

임포터에서 사용자 매핑을 구현하는 방법#

  1. 임포터 내에서 사용자 매핑에 대한 기능 플래그를 구현합니다.

  2. 임포트 중에 플래그 상태를 변경하더라도 진행 중인 임포트에 영향을 미치지 않도록 임포터 시작 시 기능 플래그 상태가 저장되는지 확인합니다.

  3. 데이터베이스에 사용자 기여를 저장하기 전에 Gitlab::Import::SourceUserMapper#find_or_create_source_user를 사용하여 기여를 귀속할 올바른 User를 찾습니다.

  4. 매핑된 사용자와 함께 기여를 데이터베이스에 저장합니다.

  5. 레코드가 지속되면 Import::PlaceholderReferences::PushService를 초기화하고 실행하여 초기화된 Import::SourceUserPlaceholderReference를 Redis에 푸시합니다. from_record를 사용하여 서비스를 초기화하는 것이 가장 편리한 경우가 많습니다.

  6. LoadPlaceholderReferencesWorker를 사용하여 캐시된 Import::SourceUserPlaceholderReference를 비동기적으로 지속합니다. 이 워커는 Import::PlaceholderReferences::LoadService를 사용하여 플레이스홀더 참조를 지속합니다. 스테이지 끝 및 임포트 끝에 주기적으로 이 워커를 호출하는 것이 좋습니다.

    [!note] 플레이스홀더 사용자 참조는 import_source_user_placeholder_references 테이블에 대한 동시 쓰기가 너무 많아지는 것을 방지하기 위해 로드하기 전에 캐시됩니다. 데이터베이스 레코드가 플레이스홀더 사용자의 ID를 참조하지만 어떤 이유로든 플레이스홀더 참조가 지속되지 않으면 기여를 재할당할 수 없으며 플레이스홀더 사용자가 삭제되지 않을 수 있습니다.

  7. 캐시된 모든 플레이스홀더 참조가 로드될 때까지 임포트 완료를 지연합니다.

    • 병렬 서드파티 임포터는 프로젝트에 대한 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로 교체하는 프로세스가 시작됩니다:

  1. Import::SourceUsers::AcceptReassignmentService가 비동기적으로 Import::ReassignPlaceholderUserRecordsWorker를 호출합니다.

  2. 워커는 Import::ReassignPlaceholderUserRecordsService를 실행하여 소스 사용자에 속하는 플레이스홀더 참조와 플레이스홀더 멤버십을 쿼리하여 플레이스홀더 사용자 ID에 대한 외래 키 참조가 있는 레코드를 재할당자의 사용자 ID로 교체합니다.

  3. 서비스는 성공적인 재할당 후 플레이스홀더 참조와 플레이스홀더 멤버십을 삭제합니다.

  4. 서비스는 소스 사용자의 상태를 complete로 설정합니다.

    [!note]

    • 플레이스홀더 참조를 재할당할 수 없는 유효한 시나리오가 있습니다. 예를 들어 플레이스홀더 사용자 리뷰어와 함께 머지 리퀘스트에 리뷰어로 사용자가 추가된 다음, 이미 리뷰어였던 플레이스홀더에 재할당을 수락하는 경우. 이는 기여 재할당 중에 ActiveRecord::RecordNotUnique 오류를 발생시키지만 유효한 시나리오입니다.
    • 처리되지 않은 오류로 인해 재할당이 실패할 가능성이 있습니다. 재할당은 항상 성공해야 하므로 이 문제를 조사해야 합니다.
  5. 서비스가 완료되면 워커는 플레이스홀더 사용자를 삭제하기 위해 비동기적으로 Import::DeletePlaceholderUserWorker를 호출합니다. 플레이스홀더 사용자 ID가 가져온 테이블에 여전히 참조되어 있으면 삭제되지 않습니다. 예외에 대해서는 AliasResolvercolumns_ignored_on_deletion을 확인하십시오.

  6. 재할당된 사용자의 확인 없이 플레이스홀더 사용자가 재할당된 경우, 재할당된 사용자에게 기여가 재할당되었음을 알리는 이메일이 전송됩니다.

플레이스홀더 참조 별칭 지정#

모델 이름과 열 이름을 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_idsnippets 테이블에 추가됩니다. 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_idsnippets 테이블에서 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_idauthor_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에 나열되어 있는지 확인하도록 설정되어 있습니다.

사용자 기여 매핑 개발자 문서

원문 보기
요약

사용자 기여 매핑은 가져오기 전에 소스에 있는 사용자가 공개 이메일로 프로비저닝되어 있지 않아도 가져온 레코드를 사용자에게 귀속시킬 수 있는 기능입니다. 사용자 기여 매핑은 마이그레이션 중에 플레이스홀더 사용자에게 기여를 할당하기 위해 각 임포터 내에서 구현되지만, 이 매핑을 사용하는 모든 임포터에 동일한 프로세스가 적용됩니다.

사용자 기여 매핑은 가져오기 전에 소스에 있는 사용자가 공개 이메일로 프로비저닝되어 있지 않아도 가져온 레코드를 사용자에게 귀속시킬 수 있는 기능입니다. 대신, 가져오기가 완료된 후 해당 기여에 실제 사용자를 할당할 수 있을 때까지 가져온 레코드에서 자리 표시자 역할을 하는 더미 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: 중복 소스 사용자가 감지될 때 발생

가져온 기여에 대한 사용자 매핑 할당 플로우#

Mermaid 다이어그램 (78줄)
소스 코드 보기
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 --&gt; FetchInfo
FetchInfo --&gt; FetchUserInfo
FetchUserInfo --&gt; CheckSourceUser
CheckSourceUser -- Yes --&gt; AssignToUser
CheckSourceUser -- No --&gt; PlaceholderLimit
PlaceholderLimit -- No --&gt; CreatePlaceholderUser
CreatePlaceholderUser --&gt; AssignContributionPlaceholder
PlaceholderLimit -- Yes --&gt; AssignImportUser
AssignToUser--&gt;ImportContribution
AssignContributionPlaceholder--&gt;ImportContribution
AssignImportUser--&gt;ImportContribution
ImportContribution--&gt;PushPlaceholderReference
PushPlaceholderReference--&gt;LoadPlaceholderReference</code></pre></details></div>

임포터에서 사용자 매핑을 구현하는 방법#

  1. 임포터 내에서 사용자 매핑에 대한 기능 플래그를 구현합니다.

  2. 임포트 중에 플래그 상태를 변경하더라도 진행 중인 임포트에 영향을 미치지 않도록 임포터 시작 시 기능 플래그 상태가 저장되는지 확인합니다.

  3. 데이터베이스에 사용자 기여를 저장하기 전에 Gitlab::Import::SourceUserMapper#find_or_create_source_user를 사용하여 기여를 귀속할 올바른 User를 찾습니다.

  4. 매핑된 사용자와 함께 기여를 데이터베이스에 저장합니다.

  5. 레코드가 지속되면 Import::PlaceholderReferences::PushService를 초기화하고 실행하여 초기화된 Import::SourceUserPlaceholderReference를 Redis에 푸시합니다. from_record를 사용하여 서비스를 초기화하는 것이 가장 편리한 경우가 많습니다.

  6. LoadPlaceholderReferencesWorker를 사용하여 캐시된 Import::SourceUserPlaceholderReference를 비동기적으로 지속합니다. 이 워커는 Import::PlaceholderReferences::LoadService를 사용하여 플레이스홀더 참조를 지속합니다. 스테이지 끝 및 임포트 끝에 주기적으로 이 워커를 호출하는 것이 좋습니다.

    [!note] 플레이스홀더 사용자 참조는 import_source_user_placeholder_references 테이블에 대한 동시 쓰기가 너무 많아지는 것을 방지하기 위해 로드하기 전에 캐시됩니다. 데이터베이스 레코드가 플레이스홀더 사용자의 ID를 참조하지만 어떤 이유로든 플레이스홀더 참조가 지속되지 않으면 기여를 재할당할 수 없으며 플레이스홀더 사용자가 삭제되지 않을 수 있습니다.

  7. 캐시된 모든 플레이스홀더 참조가 로드될 때까지 임포트 완료를 지연합니다.

    • 병렬 서드파티 임포터는 프로젝트에 대한 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로 교체하는 프로세스가 시작됩니다:

  1. Import::SourceUsers::AcceptReassignmentService가 비동기적으로 Import::ReassignPlaceholderUserRecordsWorker를 호출합니다.

  2. 워커는 Import::ReassignPlaceholderUserRecordsService를 실행하여 소스 사용자에 속하는 플레이스홀더 참조와 플레이스홀더 멤버십을 쿼리하여 플레이스홀더 사용자 ID에 대한 외래 키 참조가 있는 레코드를 재할당자의 사용자 ID로 교체합니다.

  3. 서비스는 성공적인 재할당 후 플레이스홀더 참조와 플레이스홀더 멤버십을 삭제합니다.

  4. 서비스는 소스 사용자의 상태를 complete로 설정합니다.

    [!note]

    • 플레이스홀더 참조를 재할당할 수 없는 유효한 시나리오가 있습니다. 예를 들어 플레이스홀더 사용자 리뷰어와 함께 머지 리퀘스트에 리뷰어로 사용자가 추가된 다음, 이미 리뷰어였던 플레이스홀더에 재할당을 수락하는 경우. 이는 기여 재할당 중에 ActiveRecord::RecordNotUnique 오류를 발생시키지만 유효한 시나리오입니다.
    • 처리되지 않은 오류로 인해 재할당이 실패할 가능성이 있습니다. 재할당은 항상 성공해야 하므로 이 문제를 조사해야 합니다.
  5. 서비스가 완료되면 워커는 플레이스홀더 사용자를 삭제하기 위해 비동기적으로 Import::DeletePlaceholderUserWorker를 호출합니다. 플레이스홀더 사용자 ID가 가져온 테이블에 여전히 참조되어 있으면 삭제되지 않습니다. 예외에 대해서는 AliasResolvercolumns_ignored_on_deletion을 확인하십시오.

  6. 재할당된 사용자의 확인 없이 플레이스홀더 사용자가 재할당된 경우, 재할당된 사용자에게 기여가 재할당되었음을 알리는 이메일이 전송됩니다.

플레이스홀더 참조 별칭 지정#

모델 이름과 열 이름을 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_idsnippets 테이블에 추가됩니다. 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_idsnippets 테이블에서 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_idauthor_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에 나열되어 있는지 확인하도록 설정되어 있습니다.