InfoGrab DocsInfoGrab Docs

다이렉트 트랜스퍼 임포터에 새 관계 추가하기

요약

개략적으로 설명하면, 다이렉트 트랜스퍼 임포터에 새 관계를 추가하려면 다음 작업이 필요합니다: 내보내기 데이터 목록에 새 관계를 추가합니다. 가져오기 측에 데이터 처리 지침이 포함된 새로운 ETL(Extract/Transform/Load) 파이프라인을 추가합니다.

개략적으로 설명하면, 다이렉트 트랜스퍼 임포터에 새 관계를 추가하려면 다음 작업이 필요합니다:

  • 내보내기 데이터 목록에 새 관계를 추가합니다.

  • 가져오기 측에 데이터 처리 지침이 포함된 새로운 ETL(Extract/Transform/Load) 파이프라인을 추가합니다.

  • 새로 생성된 파이프라인을 가져오기 Stage 목록에 추가합니다.

  • UI에 표시할 새로 생성된 관계의 라벨을 추가합니다.

  • 충분한 테스트 커버리지를 확보합니다.

버그 및 성능 문제 도입 위험을 줄이기 위해, 새로 추가된 관계는 기능 플래그 뒤에 배치해야 합니다.

소스에서 내보내기#

내보내는 관계의 유형은 다음과 같습니다:

  • ActiveRecord 연관관계. import_export.yml 파일에서 읽어 JSON으로 직렬화하고 NDJSON 파일에 씁니다. 각 관계는 .gz 파일 또는 컬렉션인 경우 .tar.gz 파일로 내보내져 업로드되며, GitLab 대상 인스턴스의 REST API를 사용하여 다운로드 및 가져오기에 제공됩니다.

  • 바이너리 파일. 예를 들어 업로드 파일이나 LFS 오브젝트 등입니다.

  • 내보내지 않고 가져오기 중 GraphQL API에서 직접 읽는 소수의 관계.

ActiveRecord 연관관계의 경우 성능상의 이유로 GraphQL API 대신 NDJSON을 사용해야 합니다. 깊게 중첩된 연관관계는 많은 네트워크 요청을 생성하여 전체 마이그레이션 속도를 저하시킬 수 있습니다.

ActiveRecord 관계 내보내기#

다이렉트 트랜스퍼 임포터의 기본 동작은 파일 기반 임포터에 크게 기반하며, 내보내기에 포함할 Project 연관관계 목록을 설명하는 import_export.yml 파일을 사용합니다. Group에는 유사한 import_export.yml이 있습니다.

예를 들어 documents라는 새로운 Project 연관관계에 대한 가져오기 지원을 추가하려면 다음을 수행해야 합니다:

  • import_export.yml 파일에 추가합니다.

  • 새 관계에 대한 테스트 커버리지를 추가합니다.

  • 추가된 관계가 예상대로 내보내지는지 확인합니다.

import_export.yml 파일에 추가하기#

이 파일에 나열된 연관관계는 위에서 아래 순서로 가져옵니다. 순서에 의존하는 연관관계가 있는 경우, 해당 연관관계가 필요한 연관관계보다 앞에 의존성을 배치하십시오. 예를 들어, 문서(documents)는 머지 리퀘스트(merge requests)보다 먼저 가져와야 합니다. 그렇지 않으면 유효하지 않습니다.

  • import_export.ymltree.project에 연관관계를 추가합니다.
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index 43d66e0e67b7..0880a27dfce2 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -122,6 +122,7 @@ tree:
         - label:
           - :priorities
     - :service_desk_setting
+    - :documents
   group_members:
     - :user

연관관계가 Enterprise Edition 전용 기능과 관련된 경우, 파일 끝의 ee.tree.project 트리에 추가하여 Enterprise Edition GitLab 인스턴스에서만 내보내고 가져올 수 있도록 합니다.

연관관계에 하위 관계가 필요 없다면 이것으로 충분합니다. 하지만 더 많은 하위 관계를 포함해야 하는 경우(예: notes), 해당 관계를 나열해야 합니다. 예를 들어, documents에 notes(notes에 대한 award emoji 포함)와 award emoji(documents에 대한)가 있어 마이그레이션하고자 한다면, 관계는 다음과 같이 됩니다:

diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index 43d66e0e67b7..0880a27dfce2 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -122,6 +122,7 @@ tree:
         - label:
           - :priorities
     - :service_desk_setting
+    - documents:
       - :award_emoji
       - notes:
         - :award_emoji
   group_members:
     - :user

  • 관계의 included_attributes를 추가합니다. 기본적으로 YAML 파일의 included_attributes에 나열되지 않은 관계 속성은 내보내기 및 가져오기 양측에서 필터링됩니다. 필요한 속성을 포함하려면 다음과 같이 included_attributes 목록에 추가해야 합니다:
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index 43d66e0e67b7..dbf0e1275ecf 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -142,6 +142,9 @@ import_only_tree:

 # Only include the following attributes for the models specified.
 included_attributes:
+  documents:
+    - :title
+    - :description
   user:
     - :id
     - :public_email

  • 관계의 excluded_attributes를 추가합니다. 파일에는 excluded_attributes 목록도 있습니다. Project에 대해서는 제외 속성을 추가할 필요가 없지만, Group에 대해서는 여전히 수행해야 합니다. 이 목록은 내보내기에 포함하지 않고 가져오기에서 무시해야 하는 속성을 나타냅니다. 이러한 속성은 주로 다음과 같습니다:

_id 또는 _ids로 끝나는 모든 것

  • attributes를 포함하는 모든 것(custom_attributes 제외)

  • _html로 끝나는 모든 것

  • 민감한 정보(예: 토큰, 암호화된 데이터)

금지된 참조의 전체 목록을 참조하십시오.

  • 관계의 methods를 추가합니다. 관계에 내보내야 하는 메서드(예: document.signature)가 있는 경우 methods 섹션에 추가할 수 있습니다. 내보낸 값은 내보내기에 포함되며, 가져오기 시 특정 필드에 할당하는 등의 작업을 수행할 수 있습니다.

예를 들어, note_diff_file.diff_export 메서드의 반환값을 내보내고, 가져오기 시 note_diff_file.diff를 설정하여 이 메서드의 내보낸 값으로 지정합니다.

새 관계에 대한 테스트 커버리지 추가#

다이렉트 트랜스퍼는 파일 기반 임포터를 내부적으로 사용하므로, 파일 기반 임포터 범위 내에서 테스트를 통해 새 관계에 대한 테스트 커버리지를 추가해야 합니다. 이 테스트는 다이렉트 트랜스퍼 임포터의 내보내기 측도 커버합니다. 다음에 테스트를 추가하십시오:

  • spec/lib/gitlab/import_export/project/tree_saver_spec.rb. Group에도 유사한 파일이 있습니다.

  • EE 전용 관계의 경우 ee/spec/lib/ee/gitlab/import_export/project/tree_saver_spec.rb.

다른 관계의 예시를 따라 새 테스트를 추가합니다.

추가된 관계가 예상대로 내보내지는지 확인하기#

import_export.yml에 지정된 새로 추가된 관계는 자동으로 디스크에 작성되는 내보내기 파일에 추가되므로 추가 작업이 필요하지 않습니다.

관계가 추가되고 테스트가 추가되면, 해당 관계가 내보내지는지 수동으로 확인할 수 있습니다. 다음 두 가지 모두에 자동으로 포함되어야 합니다:

  • 파일 기반 가져오기 및 내보내기. 프로젝트 내보내기 기능을 사용하여 내보내고, 다운로드하고, 내보낸 데이터를 검사합니다.

  • 다이렉트 트랜스퍼 내보내기. export_relations API를 사용하여 내보내고, 다운로드하고, 내보낸 관계를 검사합니다(배치 단위로 내보내질 수 있음).

바이너리 관계 내보내기#

바이너리 관계에 대한 지원을 추가하는 경우:

예시

대상에서 가져오기#

위에서 언급했듯이, 다이렉트 트랜스퍼 가져오기에는 세 가지 유형의 관계가 있습니다:

  • NDJSON으로 내보낸 관계, export_relations API에서 다운로드. 예: documents.ndjson.gz.

  • GraphQL API 관계. 예: members 정보는 그룹 및 프로젝트 사용자 멤버십을 가져오기 위해 GraphQL을 사용하여 가져옵니다.

  • 바이너리 관계, export_relations API에서 다운로드. 예: lfs_objects.tar.gz.

다이렉트 트랜스퍼 임포터는 Extract/Transform/Load 데이터 처리 기법을 기반으로 하므로, 관계 가져오기를 시작하려면 다음을 정의해야 합니다:

  • 새로운 관계 가져오기 파이프라인. 예: DocumentsPipeline.

  • 파이프라인이 데이터를 추출할 위치와 방법을 알 수 있는 데이터 추출기. 예: NdjsonPipeline.

  • 데이터를 필요한 형식으로 변환하는 클래스 집합인 트랜스포머(transformers) 목록.

  • 데이터를 어딘가에 저장하는 로더(loader). 예: 데이터베이스에 행을 저장하거나 새 LFS 오브젝트를 생성.

어떤 유형의 관계를 가져오더라도, Pipeline 클래스 구조는 동일합니다:

module BulkImports
  module Common
    module Pipelines
      class DocumentsPipeline
        include Pipeline

        def extract(context)
          BulkImports::Pipeline::ExtractedData.new(data: file_paths)
        end

        def transform(context, object)
          ...
        end

        def load(context, object)
          document.save!
        end
      end
    end
  end
end

NDJSON에서 관계 가져오기#

파이프라인 정의하기#

앞선 예시에서 documents 관계는 NDJSON 파일로 내보내집니다. 이 경우 다음 두 가지를 모두 사용할 수 있습니다:

  • NdjsonPipeline: JSON에서 ActiveRecord 오브젝트로 자동 데이터 변환을 포함합니다(내부적으로 파일 기반 임포터를 사용).

  • NdjsonExtractor: /export_relations/download REST API 엔드포인트를 사용하여 소스 인스턴스에서 .ndjson.gz 파일을 다운로드합니다.

ETL 파이프라인의 각 단계는 메서드 또는 클래스로 정의할 수 있습니다.

  class DocumentsPipeline
    include NdjsonPipeline

    relation_name 'documents'

    extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
end

이 새 파이프라인은 이제 다음을 수행합니다:

  • 소스 인스턴스에서 documents.ndjson.gz 파일을 다운로드합니다.

  • NDJSON 파일의 내용을 읽고 JSON을 역직렬화하여 ActiveRecord 오브젝트로 변환합니다.

  • 프로젝트 범위 내에서 데이터베이스에 저장합니다.

파이프라인은 다음 중 하나에 배치할 수 있습니다:

  • 그룹과 프로젝트 마이그레이션 모두에서 공유되어 사용되는 경우 BulkImports::Common::Pipelines 네임스페이스. 예를 들어, LabelsPipeline은 공통 파이프라인으로 그룹 및 프로젝트 Stage 목록 모두에서 참조됩니다.

  • 파이프라인이 프로젝트 마이그레이션에 속하는 경우 BulkImports::Projects::Pipelines 네임스페이스.

  • 파이프라인이 그룹 마이그레이션에 속하는 경우 BulkImports::Groups::Pipelines 네임스페이스.

Stage에 새 파이프라인 추가하기#

다이렉트 트랜스퍼 임포터는 그룹과 프로젝트 마이그레이션을 Stage별로 수행합니다. Stage 목록은 다음에 정의되어 있습니다:

  • Project의 경우: lib/bulk_imports/projects/stage.rb.

  • Group의 경우: lib/bulk_imports/groups/stage.rb.

각 Stage는:

  • 병렬로 실행되는 여러 파이프라인을 가질 수 있습니다.

  • 다음 Stage로 넘어가기 전에 완전히 완료되어야 합니다.

Project Stage에 파이프라인을 추가해 보겠습니다:

module BulkImports
  module Projects
    class Stage < ::BulkImports::Stage
      private

       def config
        {
          project: {
            pipeline: BulkImports::Projects::Pipelines::ProjectPipeline,
            stage: 0
          },
          repository: {
            pipeline: BulkImports::Projects::Pipelines::RepositoryPipeline,
            maximum_source_version: '15.0.0',
            stage: 1
          },
          documents: {
            pipeline: BulkImports::Projects::Pipelines::DocumentsPipeline,
            minimum_source_version: '16.11.0',
            stage: 2
          }
       end
    end
  end
end

다음을 지정했습니다:

  • stage: 2: 프로젝트 및 리포지터리 Stage가 먼저 완료되어야 파이프라인이 Stage 2에서 실행됩니다.

  • minimum_source_version: '16.11.0': 이 마일스톤에서 내보내기용 documents 관계를 도입했으므로 이전 GitLab 버전에서는 사용할 수 없습니다. 따라서 이 파이프라인은 소스 버전이 16.11 이상인 경우에만 실행됩니다.

관계가 더 이상 사용되지 않고 특정 버전까지만 파이프라인을 실행해야 하는 경우, maximum_source_version 속성을 지정할 수 있습니다.

파이프라인에 테스트 커버리지 추가하기#

이미 내보내기 측을 테스트로 커버했으므로, 가져오기 측도 동일하게 해야 합니다. 다이렉트 트랜스퍼 임포터의 경우, 각 파이프라인은 이 예시와 유사한 별도의 spec 파일을 가집니다.

예시

사용자 정의 연관관계 이름으로 관계 가져오기#

ActiveRecord 클래스 이름과 일치하지 않는 연관관계가 있습니다. 예를 들어:

class Release
  has_many :links, class_name: 'Releases::Link'
end

이러한 연관관계는 releases.ndjson에서 links로 내보내집니다. 그러나 가져오기 시, 관계 클래스를 상수화할 때 links는 클래스가 존재하지 않으므로 상수화할 수 없습니다. 클래스는 Releases::Link여야 합니다.

이 경우, 임포터가 올바르게 상수화하는 방법을 알 수 있도록 연관관계와 해당 ActiveRecord 클래스의 맵을 나타내는 OVERRIDES 해시에 이 연관관계 이름을 추가해야 합니다.

module Gitlab
  module ImportExport
    module Project
      class RelationFactory < Base::RelationFactory
        OVERRIDES = {
          links: 'Releases::Link'
        }
      end
    end
  end
end

이 방법으로 임포터는 내보낸 각 link를 해당 Releases::Link 클래스에 매핑합니다.

여러 다른 관계에서 참조되는 기존 오브젝트 가져오기#

관계가 여러 연관관계에 걸쳐(또는 단일 연관관계 내 여러 레코드에 걸쳐) 참조되는 경우, 중복을 가져오지 않도록 해야 합니다.

예를 들어, 여러 이슈와 머지 리퀘스트에 적용된 라벨을 고려해 보십시오. 이슈와 머지 리퀘스트를 내보낼 때, 내보낸 라벨은 각 레코드 내에 하위 관계로 포함됩니다. 내보낸 이슈와 머지 리퀘스트를 가져올 때, 라벨을 한 번만 가져오고 모든 레코드에서 재사용하고자 합니다. 그렇지 않으면 중복(같은 이름의 여러 라벨)이 발생합니다.

이런 오브젝트를 한 번만 가져오고 여러 곳에서 재사용하려면, 해당 오브젝트를 기존 오브젝트 관계로 정의해야 합니다.

먼저, EXISTING_OBJECT_RELATIONS에 라벨 연관관계를 추가해야 합니다. 관계가 기존 오브젝트 관계 목록에 추가되면, 임포터는 이런 관계를 다른 관계와 다르게 처리해야 한다는 것을 알고 다른 가져오기 흐름을 거칩니다. 이런 관계를 일반 경로로 가져오는 대신, ObjectBuilder를 사용합니다.

ObjectBuilder는 다음 중 하나를 시도합니다:

  • 정의한 파라미터를 기반으로 데이터베이스에서 기존 오브젝트를 찾아 반환합니다.

  • 존재하지 않으면 새로 생성합니다.

ObjectBuilder에 새 관계를 추가하려면 다음을 수행해야 합니다:

  • 위에서 언급한 대로 EXISTING_OBJECT_RELATIONS에 관계를 추가합니다.

  • 프로젝트 또는 그룹 연관관계 여부에 따라 그룹 또는 프로젝트 ObjectBuilder를 업데이트합니다.

  • 기존 오브젝트 조회에 사용할 속성을 정의합니다. 예를 들어, 라벨의 경우 title, description, created_at으로 검색합니다. 정의된 파라미터를 가진 라벨이 프로젝트에 존재하면 재사용하고 새로 생성하지 않습니다.

GraphQL API에서 관계 가져오기#

GraphQL API를 통해 관계를 사용할 수 있는 경우, GraphQlExtractor를 사용하고 파이프라인 클래스 내에서 변환 및 로딩을 수행할 수 있습니다.

MembersPipeline 예시:

module BulkImports
  module Common
    module Pipelines
      class MembersPipeline
        include Pipeline

        transformer Common::Transformers::ProhibitedAttributesTransformer
        transformer Common::Transformers::MemberAttributesTransformer

        def extract(context)
          graphql_extractor.extract(context)
        end

        def load(_context, data)
          ...

          member.save!
        end

        private

        def graphql_extractor
          @graphql_extractor ||= BulkImports::Common::Extractors::GraphqlExtractor
            .new(query: BulkImports::Common::Graphql::GetMembersQuery)
        end
      end
    end
  end
end

나머지 단계는 위의 단계와 동일합니다.

바이너리 관계 가져오기#

바이너리 관계 파이프라인은 다른 파이프라인과 동일한 구조를 가지며, extract/transform/load 단계에서 무슨 일이 일어나는지 정의하기만 하면 됩니다.

LfsObjectsPipeline 예시:

module BulkImports
  module Common
    module Pipelines
      class LfsObjectsPipeline
        include Pipeline

        file_extraction_pipeline!

        def extract(_context)
          download_service.execute
          decompression_service.execute
          extraction_service.execute

          ...
        end

        def load(_context, file_path)
          ...

          lfs_object.save!
        end
      end
    end
  end
end

데이터 다운로드를 지원하는 여러 헬퍼 서비스 클래스가 있습니다:

  • BulkImports::FileDownloadService: 지정된 위치에서 파일을 다운로드합니다.

  • BulkImports::FileDecompressionService: 필수 유효성 검사가 포함된 Gzip 압축 해제 서비스.

  • BulkImports::ArchiveExtractionService: Tar 추출 서비스.

UI 적용#

새 관계에 라벨 추가하기#

다이렉트 트랜스퍼에 새 관계가 추가되면, UI에서 사람이 읽을 수 있는 형식으로 관계가 표시되는지 확인해야 합니다.

diff --git a/app/assets/javascripts/import/constants.js b/app/assets/javascripts/import/constants.js
index 439f453cd9d3..d6b4119a0af9 100644
--- a/app/assets/javascripts/import/constants.js
+++ b/app/assets/javascripts/import/constants.js
@@ -31,6 +31,7 @@ export const BULK_IMPORT_STATIC_ITEMS = {
   service_desk_setting: __('Service Desk'),
   vulnerabilities: __('Vulnerabilities'),
   commit_notes: __('Commit notes'),
+  documents: __('Documents')
 };

 const STATISTIC_ITEMS = {

다이렉트 트랜스퍼 임포터에 새 관계 추가하기

GitLab v19.1
원문 보기
요약

개략적으로 설명하면, 다이렉트 트랜스퍼 임포터에 새 관계를 추가하려면 다음 작업이 필요합니다: 내보내기 데이터 목록에 새 관계를 추가합니다. 가져오기 측에 데이터 처리 지침이 포함된 새로운 ETL(Extract/Transform/Load) 파이프라인을 추가합니다.

개략적으로 설명하면, 다이렉트 트랜스퍼 임포터에 새 관계를 추가하려면 다음 작업이 필요합니다:

  • 내보내기 데이터 목록에 새 관계를 추가합니다.

  • 가져오기 측에 데이터 처리 지침이 포함된 새로운 ETL(Extract/Transform/Load) 파이프라인을 추가합니다.

  • 새로 생성된 파이프라인을 가져오기 Stage 목록에 추가합니다.

  • UI에 표시할 새로 생성된 관계의 라벨을 추가합니다.

  • 충분한 테스트 커버리지를 확보합니다.

버그 및 성능 문제 도입 위험을 줄이기 위해, 새로 추가된 관계는 기능 플래그 뒤에 배치해야 합니다.

소스에서 내보내기#

내보내는 관계의 유형은 다음과 같습니다:

  • ActiveRecord 연관관계. import_export.yml 파일에서 읽어 JSON으로 직렬화하고 NDJSON 파일에 씁니다. 각 관계는 .gz 파일 또는 컬렉션인 경우 .tar.gz 파일로 내보내져 업로드되며, GitLab 대상 인스턴스의 REST API를 사용하여 다운로드 및 가져오기에 제공됩니다.

  • 바이너리 파일. 예를 들어 업로드 파일이나 LFS 오브젝트 등입니다.

  • 내보내지 않고 가져오기 중 GraphQL API에서 직접 읽는 소수의 관계.

ActiveRecord 연관관계의 경우 성능상의 이유로 GraphQL API 대신 NDJSON을 사용해야 합니다. 깊게 중첩된 연관관계는 많은 네트워크 요청을 생성하여 전체 마이그레이션 속도를 저하시킬 수 있습니다.

ActiveRecord 관계 내보내기#

다이렉트 트랜스퍼 임포터의 기본 동작은 파일 기반 임포터에 크게 기반하며, 내보내기에 포함할 Project 연관관계 목록을 설명하는 import_export.yml 파일을 사용합니다. Group에는 유사한 import_export.yml이 있습니다.

예를 들어 documents라는 새로운 Project 연관관계에 대한 가져오기 지원을 추가하려면 다음을 수행해야 합니다:

  • import_export.yml 파일에 추가합니다.

  • 새 관계에 대한 테스트 커버리지를 추가합니다.

  • 추가된 관계가 예상대로 내보내지는지 확인합니다.

import_export.yml 파일에 추가하기#

이 파일에 나열된 연관관계는 위에서 아래 순서로 가져옵니다. 순서에 의존하는 연관관계가 있는 경우, 해당 연관관계가 필요한 연관관계보다 앞에 의존성을 배치하십시오. 예를 들어, 문서(documents)는 머지 리퀘스트(merge requests)보다 먼저 가져와야 합니다. 그렇지 않으면 유효하지 않습니다.

  • import_export.ymltree.project에 연관관계를 추가합니다.
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index 43d66e0e67b7..0880a27dfce2 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -122,6 +122,7 @@ tree:
         - label:
           - :priorities
     - :service_desk_setting
+    - :documents
   group_members:
     - :user

연관관계가 Enterprise Edition 전용 기능과 관련된 경우, 파일 끝의 ee.tree.project 트리에 추가하여 Enterprise Edition GitLab 인스턴스에서만 내보내고 가져올 수 있도록 합니다.

연관관계에 하위 관계가 필요 없다면 이것으로 충분합니다. 하지만 더 많은 하위 관계를 포함해야 하는 경우(예: notes), 해당 관계를 나열해야 합니다. 예를 들어, documents에 notes(notes에 대한 award emoji 포함)와 award emoji(documents에 대한)가 있어 마이그레이션하고자 한다면, 관계는 다음과 같이 됩니다:

diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index 43d66e0e67b7..0880a27dfce2 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -122,6 +122,7 @@ tree:
         - label:
           - :priorities
     - :service_desk_setting
+    - documents:
       - :award_emoji
       - notes:
         - :award_emoji
   group_members:
     - :user

  • 관계의 included_attributes를 추가합니다. 기본적으로 YAML 파일의 included_attributes에 나열되지 않은 관계 속성은 내보내기 및 가져오기 양측에서 필터링됩니다. 필요한 속성을 포함하려면 다음과 같이 included_attributes 목록에 추가해야 합니다:
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index 43d66e0e67b7..dbf0e1275ecf 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -142,6 +142,9 @@ import_only_tree:

 # Only include the following attributes for the models specified.
 included_attributes:
+  documents:
+    - :title
+    - :description
   user:
     - :id
     - :public_email

  • 관계의 excluded_attributes를 추가합니다. 파일에는 excluded_attributes 목록도 있습니다. Project에 대해서는 제외 속성을 추가할 필요가 없지만, Group에 대해서는 여전히 수행해야 합니다. 이 목록은 내보내기에 포함하지 않고 가져오기에서 무시해야 하는 속성을 나타냅니다. 이러한 속성은 주로 다음과 같습니다:

_id 또는 _ids로 끝나는 모든 것

  • attributes를 포함하는 모든 것(custom_attributes 제외)

  • _html로 끝나는 모든 것

  • 민감한 정보(예: 토큰, 암호화된 데이터)

금지된 참조의 전체 목록을 참조하십시오.

  • 관계의 methods를 추가합니다. 관계에 내보내야 하는 메서드(예: document.signature)가 있는 경우 methods 섹션에 추가할 수 있습니다. 내보낸 값은 내보내기에 포함되며, 가져오기 시 특정 필드에 할당하는 등의 작업을 수행할 수 있습니다.

예를 들어, note_diff_file.diff_export 메서드의 반환값을 내보내고, 가져오기 시 note_diff_file.diff를 설정하여 이 메서드의 내보낸 값으로 지정합니다.

새 관계에 대한 테스트 커버리지 추가#

다이렉트 트랜스퍼는 파일 기반 임포터를 내부적으로 사용하므로, 파일 기반 임포터 범위 내에서 테스트를 통해 새 관계에 대한 테스트 커버리지를 추가해야 합니다. 이 테스트는 다이렉트 트랜스퍼 임포터의 내보내기 측도 커버합니다. 다음에 테스트를 추가하십시오:

  • spec/lib/gitlab/import_export/project/tree_saver_spec.rb. Group에도 유사한 파일이 있습니다.

  • EE 전용 관계의 경우 ee/spec/lib/ee/gitlab/import_export/project/tree_saver_spec.rb.

다른 관계의 예시를 따라 새 테스트를 추가합니다.

추가된 관계가 예상대로 내보내지는지 확인하기#

import_export.yml에 지정된 새로 추가된 관계는 자동으로 디스크에 작성되는 내보내기 파일에 추가되므로 추가 작업이 필요하지 않습니다.

관계가 추가되고 테스트가 추가되면, 해당 관계가 내보내지는지 수동으로 확인할 수 있습니다. 다음 두 가지 모두에 자동으로 포함되어야 합니다:

  • 파일 기반 가져오기 및 내보내기. 프로젝트 내보내기 기능을 사용하여 내보내고, 다운로드하고, 내보낸 데이터를 검사합니다.

  • 다이렉트 트랜스퍼 내보내기. export_relations API를 사용하여 내보내고, 다운로드하고, 내보낸 관계를 검사합니다(배치 단위로 내보내질 수 있음).

바이너리 관계 내보내기#

바이너리 관계에 대한 지원을 추가하는 경우:

예시

대상에서 가져오기#

위에서 언급했듯이, 다이렉트 트랜스퍼 가져오기에는 세 가지 유형의 관계가 있습니다:

  • NDJSON으로 내보낸 관계, export_relations API에서 다운로드. 예: documents.ndjson.gz.

  • GraphQL API 관계. 예: members 정보는 그룹 및 프로젝트 사용자 멤버십을 가져오기 위해 GraphQL을 사용하여 가져옵니다.

  • 바이너리 관계, export_relations API에서 다운로드. 예: lfs_objects.tar.gz.

다이렉트 트랜스퍼 임포터는 Extract/Transform/Load 데이터 처리 기법을 기반으로 하므로, 관계 가져오기를 시작하려면 다음을 정의해야 합니다:

  • 새로운 관계 가져오기 파이프라인. 예: DocumentsPipeline.

  • 파이프라인이 데이터를 추출할 위치와 방법을 알 수 있는 데이터 추출기. 예: NdjsonPipeline.

  • 데이터를 필요한 형식으로 변환하는 클래스 집합인 트랜스포머(transformers) 목록.

  • 데이터를 어딘가에 저장하는 로더(loader). 예: 데이터베이스에 행을 저장하거나 새 LFS 오브젝트를 생성.

어떤 유형의 관계를 가져오더라도, Pipeline 클래스 구조는 동일합니다:

module BulkImports
  module Common
    module Pipelines
      class DocumentsPipeline
        include Pipeline

        def extract(context)
          BulkImports::Pipeline::ExtractedData.new(data: file_paths)
        end

        def transform(context, object)
          ...
        end

        def load(context, object)
          document.save!
        end
      end
    end
  end
end

NDJSON에서 관계 가져오기#

파이프라인 정의하기#

앞선 예시에서 documents 관계는 NDJSON 파일로 내보내집니다. 이 경우 다음 두 가지를 모두 사용할 수 있습니다:

  • NdjsonPipeline: JSON에서 ActiveRecord 오브젝트로 자동 데이터 변환을 포함합니다(내부적으로 파일 기반 임포터를 사용).

  • NdjsonExtractor: /export_relations/download REST API 엔드포인트를 사용하여 소스 인스턴스에서 .ndjson.gz 파일을 다운로드합니다.

ETL 파이프라인의 각 단계는 메서드 또는 클래스로 정의할 수 있습니다.

  class DocumentsPipeline
    include NdjsonPipeline

    relation_name 'documents'

    extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
end

이 새 파이프라인은 이제 다음을 수행합니다:

  • 소스 인스턴스에서 documents.ndjson.gz 파일을 다운로드합니다.

  • NDJSON 파일의 내용을 읽고 JSON을 역직렬화하여 ActiveRecord 오브젝트로 변환합니다.

  • 프로젝트 범위 내에서 데이터베이스에 저장합니다.

파이프라인은 다음 중 하나에 배치할 수 있습니다:

  • 그룹과 프로젝트 마이그레이션 모두에서 공유되어 사용되는 경우 BulkImports::Common::Pipelines 네임스페이스. 예를 들어, LabelsPipeline은 공통 파이프라인으로 그룹 및 프로젝트 Stage 목록 모두에서 참조됩니다.

  • 파이프라인이 프로젝트 마이그레이션에 속하는 경우 BulkImports::Projects::Pipelines 네임스페이스.

  • 파이프라인이 그룹 마이그레이션에 속하는 경우 BulkImports::Groups::Pipelines 네임스페이스.

Stage에 새 파이프라인 추가하기#

다이렉트 트랜스퍼 임포터는 그룹과 프로젝트 마이그레이션을 Stage별로 수행합니다. Stage 목록은 다음에 정의되어 있습니다:

  • Project의 경우: lib/bulk_imports/projects/stage.rb.

  • Group의 경우: lib/bulk_imports/groups/stage.rb.

각 Stage는:

  • 병렬로 실행되는 여러 파이프라인을 가질 수 있습니다.

  • 다음 Stage로 넘어가기 전에 완전히 완료되어야 합니다.

Project Stage에 파이프라인을 추가해 보겠습니다:

module BulkImports
  module Projects
    class Stage < ::BulkImports::Stage
      private

       def config
        {
          project: {
            pipeline: BulkImports::Projects::Pipelines::ProjectPipeline,
            stage: 0
          },
          repository: {
            pipeline: BulkImports::Projects::Pipelines::RepositoryPipeline,
            maximum_source_version: '15.0.0',
            stage: 1
          },
          documents: {
            pipeline: BulkImports::Projects::Pipelines::DocumentsPipeline,
            minimum_source_version: '16.11.0',
            stage: 2
          }
       end
    end
  end
end

다음을 지정했습니다:

  • stage: 2: 프로젝트 및 리포지터리 Stage가 먼저 완료되어야 파이프라인이 Stage 2에서 실행됩니다.

  • minimum_source_version: '16.11.0': 이 마일스톤에서 내보내기용 documents 관계를 도입했으므로 이전 GitLab 버전에서는 사용할 수 없습니다. 따라서 이 파이프라인은 소스 버전이 16.11 이상인 경우에만 실행됩니다.

관계가 더 이상 사용되지 않고 특정 버전까지만 파이프라인을 실행해야 하는 경우, maximum_source_version 속성을 지정할 수 있습니다.

파이프라인에 테스트 커버리지 추가하기#

이미 내보내기 측을 테스트로 커버했으므로, 가져오기 측도 동일하게 해야 합니다. 다이렉트 트랜스퍼 임포터의 경우, 각 파이프라인은 이 예시와 유사한 별도의 spec 파일을 가집니다.

예시

사용자 정의 연관관계 이름으로 관계 가져오기#

ActiveRecord 클래스 이름과 일치하지 않는 연관관계가 있습니다. 예를 들어:

class Release
  has_many :links, class_name: 'Releases::Link'
end

이러한 연관관계는 releases.ndjson에서 links로 내보내집니다. 그러나 가져오기 시, 관계 클래스를 상수화할 때 links는 클래스가 존재하지 않으므로 상수화할 수 없습니다. 클래스는 Releases::Link여야 합니다.

이 경우, 임포터가 올바르게 상수화하는 방법을 알 수 있도록 연관관계와 해당 ActiveRecord 클래스의 맵을 나타내는 OVERRIDES 해시에 이 연관관계 이름을 추가해야 합니다.

module Gitlab
  module ImportExport
    module Project
      class RelationFactory < Base::RelationFactory
        OVERRIDES = {
          links: 'Releases::Link'
        }
      end
    end
  end
end

이 방법으로 임포터는 내보낸 각 link를 해당 Releases::Link 클래스에 매핑합니다.

여러 다른 관계에서 참조되는 기존 오브젝트 가져오기#

관계가 여러 연관관계에 걸쳐(또는 단일 연관관계 내 여러 레코드에 걸쳐) 참조되는 경우, 중복을 가져오지 않도록 해야 합니다.

예를 들어, 여러 이슈와 머지 리퀘스트에 적용된 라벨을 고려해 보십시오. 이슈와 머지 리퀘스트를 내보낼 때, 내보낸 라벨은 각 레코드 내에 하위 관계로 포함됩니다. 내보낸 이슈와 머지 리퀘스트를 가져올 때, 라벨을 한 번만 가져오고 모든 레코드에서 재사용하고자 합니다. 그렇지 않으면 중복(같은 이름의 여러 라벨)이 발생합니다.

이런 오브젝트를 한 번만 가져오고 여러 곳에서 재사용하려면, 해당 오브젝트를 기존 오브젝트 관계로 정의해야 합니다.

먼저, EXISTING_OBJECT_RELATIONS에 라벨 연관관계를 추가해야 합니다. 관계가 기존 오브젝트 관계 목록에 추가되면, 임포터는 이런 관계를 다른 관계와 다르게 처리해야 한다는 것을 알고 다른 가져오기 흐름을 거칩니다. 이런 관계를 일반 경로로 가져오는 대신, ObjectBuilder를 사용합니다.

ObjectBuilder는 다음 중 하나를 시도합니다:

  • 정의한 파라미터를 기반으로 데이터베이스에서 기존 오브젝트를 찾아 반환합니다.

  • 존재하지 않으면 새로 생성합니다.

ObjectBuilder에 새 관계를 추가하려면 다음을 수행해야 합니다:

  • 위에서 언급한 대로 EXISTING_OBJECT_RELATIONS에 관계를 추가합니다.

  • 프로젝트 또는 그룹 연관관계 여부에 따라 그룹 또는 프로젝트 ObjectBuilder를 업데이트합니다.

  • 기존 오브젝트 조회에 사용할 속성을 정의합니다. 예를 들어, 라벨의 경우 title, description, created_at으로 검색합니다. 정의된 파라미터를 가진 라벨이 프로젝트에 존재하면 재사용하고 새로 생성하지 않습니다.

GraphQL API에서 관계 가져오기#

GraphQL API를 통해 관계를 사용할 수 있는 경우, GraphQlExtractor를 사용하고 파이프라인 클래스 내에서 변환 및 로딩을 수행할 수 있습니다.

MembersPipeline 예시:

module BulkImports
  module Common
    module Pipelines
      class MembersPipeline
        include Pipeline

        transformer Common::Transformers::ProhibitedAttributesTransformer
        transformer Common::Transformers::MemberAttributesTransformer

        def extract(context)
          graphql_extractor.extract(context)
        end

        def load(_context, data)
          ...

          member.save!
        end

        private

        def graphql_extractor
          @graphql_extractor ||= BulkImports::Common::Extractors::GraphqlExtractor
            .new(query: BulkImports::Common::Graphql::GetMembersQuery)
        end
      end
    end
  end
end

나머지 단계는 위의 단계와 동일합니다.

바이너리 관계 가져오기#

바이너리 관계 파이프라인은 다른 파이프라인과 동일한 구조를 가지며, extract/transform/load 단계에서 무슨 일이 일어나는지 정의하기만 하면 됩니다.

LfsObjectsPipeline 예시:

module BulkImports
  module Common
    module Pipelines
      class LfsObjectsPipeline
        include Pipeline

        file_extraction_pipeline!

        def extract(_context)
          download_service.execute
          decompression_service.execute
          extraction_service.execute

          ...
        end

        def load(_context, file_path)
          ...

          lfs_object.save!
        end
      end
    end
  end
end

데이터 다운로드를 지원하는 여러 헬퍼 서비스 클래스가 있습니다:

  • BulkImports::FileDownloadService: 지정된 위치에서 파일을 다운로드합니다.

  • BulkImports::FileDecompressionService: 필수 유효성 검사가 포함된 Gzip 압축 해제 서비스.

  • BulkImports::ArchiveExtractionService: Tar 추출 서비스.

UI 적용#

새 관계에 라벨 추가하기#

다이렉트 트랜스퍼에 새 관계가 추가되면, UI에서 사람이 읽을 수 있는 형식으로 관계가 표시되는지 확인해야 합니다.

diff --git a/app/assets/javascripts/import/constants.js b/app/assets/javascripts/import/constants.js
index 439f453cd9d3..d6b4119a0af9 100644
--- a/app/assets/javascripts/import/constants.js
+++ b/app/assets/javascripts/import/constants.js
@@ -31,6 +31,7 @@ export const BULK_IMPORT_STATIC_ITEMS = {
   service_desk_setting: __('Service Desk'),
   vulnerabilities: __('Vulnerabilities'),
   commit_notes: __('Commit notes'),
+  documents: __('Documents')
 };

 const STATISTIC_ITEMS = {