summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNejc Habjan <nejc.habjan@siemens.com>2022-12-08 01:07:26 +0100
committerNejc Habjan <hab.nejc@gmail.com>2022-12-19 22:23:02 +0100
commit043de2d265e0e5114d1cd901f82869c003413d9b (patch)
tree90349e0fe6b282686c231adaec53cfa084480ab9
parent6fca6512a32e9e289f988900e1157dfe788f54be (diff)
downloadgitlab-043de2d265e0e5114d1cd901f82869c003413d9b.tar.gz
feat(api): add support for bulk imports API
-rw-r--r--docs/api-objects.rst1
-rw-r--r--docs/gl_objects/bulk_imports.rst82
-rw-r--r--docs/gl_objects/projects.rst2
-rw-r--r--gitlab/client.py4
-rw-r--r--gitlab/v4/objects/__init__.py1
-rw-r--r--gitlab/v4/objects/bulk_imports.py54
-rw-r--r--tests/functional/api/test_bulk_imports.py41
-rw-r--r--tests/unit/conftest.py5
-rw-r--r--tests/unit/objects/test_bulk_imports.py159
9 files changed, 349 insertions, 0 deletions
diff --git a/docs/api-objects.rst b/docs/api-objects.rst
index ede2cf8..8c7bb19 100644
--- a/docs/api-objects.rst
+++ b/docs/api-objects.rst
@@ -11,6 +11,7 @@ API examples
gl_objects/emojis
gl_objects/badges
gl_objects/branches
+ gl_objects/bulk_imports
gl_objects/messages
gl_objects/ci_lint
gl_objects/commits
diff --git a/docs/gl_objects/bulk_imports.rst b/docs/gl_objects/bulk_imports.rst
new file mode 100644
index 0000000..fa386bd
--- /dev/null
+++ b/docs/gl_objects/bulk_imports.rst
@@ -0,0 +1,82 @@
+#########################
+Migrations (Bulk Imports)
+#########################
+
+References
+----------
+
+* v4 API:
+
+ + :class:`gitlab.v4.objects.BulkImport`
+ + :class:`gitlab.v4.objects.BulkImportManager`
+ + :attr:`gitlab.Gitlab.bulk_imports`
+ + :class:`gitlab.v4.objects.BulkImportAllEntity`
+ + :class:`gitlab.v4.objects.BulkImportAllEntityManager`
+ + :attr:`gitlab.Gitlab.bulk_import_entities`
+ + :class:`gitlab.v4.objects.BulkImportEntity`
+ + :class:`gitlab.v4.objects.BulkImportEntityManager`
+ + :attr:`gitlab.v4.objects.BulkImport.entities`
+
+* GitLab API: https://docs.gitlab.com/ee/api/bulk_imports.html
+
+Examples
+--------
+
+.. note::
+
+ Like the project/group imports and exports, this is an asynchronous operation and you
+ will need to refresh the state from the server to get an accurate migration status. See
+ :ref:`project_import_export` in the import/export section for more details and examples.
+
+Start a bulk import/migration of a group and wait for completion::
+
+ # Create the migration
+ configuration = {
+ "url": "https://gitlab.example.com",
+ "access_token": private_token,
+ }
+ entity = {
+ "source_full_path": "source_group",
+ "source_type": "group_entity",
+ "destination_slug": "imported-group",
+ "destination_namespace": "imported-namespace",
+ }
+ migration = gl.bulk_imports.create(
+ {
+ "configuration": configuration,
+ "entities": [entity],
+ }
+ )
+
+ # Wait for the 'finished' status
+ while migration.status != "finished":
+ time.sleep(1)
+ migration.refresh()
+
+List all migrations::
+
+ gl.bulk_imports.list()
+
+List the entities of all migrations::
+
+ gl.bulk_import_entities.list()
+
+Get a single migration by ID::
+
+ migration = gl.bulk_imports.get(123)
+
+List the entities of a single migration::
+
+ entities = migration.entities.list()
+
+Get a single entity of a migration by ID::
+
+ entity = migration.entities.get(123)
+
+Refresh the state of a migration or entity from the server::
+
+ migration.refresh()
+ entity.refresh()
+
+ print(migration.status)
+ print(entity.status)
diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst
index 7fc0ab9..b80df0d 100644
--- a/docs/gl_objects/projects.rst
+++ b/docs/gl_objects/projects.rst
@@ -275,6 +275,8 @@ Reference
* GitLab API: https://docs.gitlab.com/ce/api/project_import_export.html
+.. _project_import_export:
+
Examples
--------
diff --git a/gitlab/client.py b/gitlab/client.py
index ebb7852..f558291 100644
--- a/gitlab/client.py
+++ b/gitlab/client.py
@@ -121,6 +121,10 @@ class Gitlab:
self.broadcastmessages = objects.BroadcastMessageManager(self)
"""See :class:`~gitlab.v4.objects.BroadcastMessageManager`"""
+ self.bulk_imports = objects.BulkImportManager(self)
+ """See :class:`~gitlab.v4.objects.BulkImportManager`"""
+ self.bulk_import_entities = objects.BulkImportAllEntityManager(self)
+ """See :class:`~gitlab.v4.objects.BulkImportAllEntityManager`"""
self.ci_lint = objects.CiLintManager(self)
"""See :class:`~gitlab.v4.objects.CiLintManager`"""
self.deploykeys = objects.DeployKeyManager(self)
diff --git a/gitlab/v4/objects/__init__.py b/gitlab/v4/objects/__init__.py
index b6f7afc..bd3ae82 100644
--- a/gitlab/v4/objects/__init__.py
+++ b/gitlab/v4/objects/__init__.py
@@ -8,6 +8,7 @@ from .badges import *
from .boards import *
from .branches import *
from .broadcast_messages import *
+from .bulk_imports import *
from .ci_lint import *
from .clusters import *
from .commits import *
diff --git a/gitlab/v4/objects/bulk_imports.py b/gitlab/v4/objects/bulk_imports.py
new file mode 100644
index 0000000..e8ef74f
--- /dev/null
+++ b/gitlab/v4/objects/bulk_imports.py
@@ -0,0 +1,54 @@
+from typing import Any, cast, Union
+
+from gitlab.base import RESTManager, RESTObject
+from gitlab.mixins import CreateMixin, ListMixin, RefreshMixin, RetrieveMixin
+from gitlab.types import RequiredOptional
+
+__all__ = [
+ "BulkImport",
+ "BulkImportManager",
+ "BulkImportAllEntity",
+ "BulkImportAllEntityManager",
+ "BulkImportEntity",
+ "BulkImportEntityManager",
+]
+
+
+class BulkImport(RefreshMixin, RESTObject):
+ entities: "BulkImportEntityManager"
+
+
+class BulkImportManager(CreateMixin, RetrieveMixin, RESTManager):
+ _path = "/bulk_imports"
+ _obj_cls = BulkImport
+ _create_attrs = RequiredOptional(required=("configuration", "entities"))
+ _list_filters = ("sort", "status")
+
+ def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> BulkImport:
+ return cast(BulkImport, super().get(id=id, lazy=lazy, **kwargs))
+
+
+class BulkImportEntity(RefreshMixin, RESTObject):
+ pass
+
+
+class BulkImportEntityManager(RetrieveMixin, RESTManager):
+ _path = "/bulk_imports/{bulk_import_id}/entities"
+ _obj_cls = BulkImportEntity
+ _from_parent_attrs = {"bulk_import_id": "id"}
+ _list_filters = ("sort", "status")
+
+ def get(
+ self, id: Union[str, int], lazy: bool = False, **kwargs: Any
+ ) -> BulkImportEntity:
+ return cast(BulkImportEntity, super().get(id=id, lazy=lazy, **kwargs))
+
+
+class BulkImportAllEntity(RESTObject):
+ pass
+
+
+class BulkImportAllEntityManager(ListMixin, RESTManager):
+ _path = "/bulk_imports/entities"
+ _obj_cls = BulkImportAllEntity
+ _list_filters = ("sort", "status")
diff --git a/tests/functional/api/test_bulk_imports.py b/tests/functional/api/test_bulk_imports.py
new file mode 100644
index 0000000..899d358
--- /dev/null
+++ b/tests/functional/api/test_bulk_imports.py
@@ -0,0 +1,41 @@
+def test_bulk_imports(gl, group):
+ destination = f"{group.full_path}-import"
+ configuration = {
+ "url": gl.url,
+ "access_token": gl.private_token,
+ }
+ migration_entity = {
+ "source_full_path": group.full_path,
+ "source_type": "group_entity",
+ "destination_slug": destination,
+ "destination_namespace": destination,
+ }
+ created_migration = gl.bulk_imports.create(
+ {
+ "configuration": configuration,
+ "entities": [migration_entity],
+ }
+ )
+
+ assert created_migration.source_type == "gitlab"
+ assert created_migration.status == "created"
+
+ migration = gl.bulk_imports.get(created_migration.id)
+ assert migration == created_migration
+
+ migration.refresh()
+ assert migration == created_migration
+
+ migrations = gl.bulk_imports.list()
+ assert migration in migrations
+
+ all_entities = gl.bulk_import_entities.list()
+ entities = migration.entities.list()
+ assert isinstance(entities, list)
+ assert entities[0] in all_entities
+
+ entity = migration.entities.get(entities[0].id)
+ assert entity == entities[0]
+
+ entity.refresh()
+ assert entity.created_at == entities[0].created_at
diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py
index 1b52d86..476e41c 100644
--- a/tests/unit/conftest.py
+++ b/tests/unit/conftest.py
@@ -97,3 +97,8 @@ def schedule(project):
@pytest.fixture
def user(gl):
return gl.users.get(1, lazy=True)
+
+
+@pytest.fixture
+def migration(gl):
+ return gl.bulk_imports.get(1, lazy=True)
diff --git a/tests/unit/objects/test_bulk_imports.py b/tests/unit/objects/test_bulk_imports.py
new file mode 100644
index 0000000..5effcdc
--- /dev/null
+++ b/tests/unit/objects/test_bulk_imports.py
@@ -0,0 +1,159 @@
+"""
+GitLab API: https://docs.gitlab.com/ce/api/bulk_imports.html
+"""
+
+import pytest
+import responses
+
+from gitlab.v4.objects import BulkImport, BulkImportAllEntity, BulkImportEntity
+
+migration_content = {
+ "id": 1,
+ "status": "finished",
+ "source_type": "gitlab",
+ "created_at": "2021-06-18T09:45:55.358Z",
+ "updated_at": "2021-06-18T09:46:27.003Z",
+}
+entity_content = {
+ "id": 1,
+ "bulk_import_id": 1,
+ "status": "finished",
+ "source_full_path": "source_group",
+ "destination_slug": "destination_slug",
+ "destination_namespace": "destination_path",
+ "parent_id": None,
+ "namespace_id": 1,
+ "project_id": None,
+ "created_at": "2021-06-18T09:47:37.390Z",
+ "updated_at": "2021-06-18T09:47:51.867Z",
+ "failures": [],
+}
+
+
+@pytest.fixture
+def resp_create_bulk_import():
+ with responses.RequestsMock() as rsps:
+ rsps.add(
+ method=responses.POST,
+ url="http://localhost/api/v4/bulk_imports",
+ json=migration_content,
+ content_type="application/json",
+ status=201,
+ )
+ yield rsps
+
+
+@pytest.fixture
+def resp_list_bulk_imports():
+ with responses.RequestsMock() as rsps:
+ rsps.add(
+ method=responses.GET,
+ url="http://localhost/api/v4/bulk_imports",
+ json=[migration_content],
+ content_type="application/json",
+ status=200,
+ )
+ yield rsps
+
+
+@pytest.fixture
+def resp_get_bulk_import():
+ with responses.RequestsMock() as rsps:
+ rsps.add(
+ method=responses.GET,
+ url="http://localhost/api/v4/bulk_imports/1",
+ json=migration_content,
+ content_type="application/json",
+ status=200,
+ )
+ yield rsps
+
+
+@pytest.fixture
+def resp_list_all_bulk_import_entities():
+ with responses.RequestsMock() as rsps:
+ rsps.add(
+ method=responses.GET,
+ url="http://localhost/api/v4/bulk_imports/entities",
+ json=[entity_content],
+ content_type="application/json",
+ status=200,
+ )
+ yield rsps
+
+
+@pytest.fixture
+def resp_list_bulk_import_entities():
+ with responses.RequestsMock() as rsps:
+ rsps.add(
+ method=responses.GET,
+ url="http://localhost/api/v4/bulk_imports/1/entities",
+ json=[entity_content],
+ content_type="application/json",
+ status=200,
+ )
+ yield rsps
+
+
+@pytest.fixture
+def resp_get_bulk_import_entity():
+ with responses.RequestsMock() as rsps:
+ rsps.add(
+ method=responses.GET,
+ url="http://localhost/api/v4/bulk_imports/1/entities/1",
+ json=entity_content,
+ content_type="application/json",
+ status=200,
+ )
+ yield rsps
+
+
+def test_create_bulk_import(gl, resp_create_bulk_import):
+ configuration = {
+ "url": gl.url,
+ "access_token": "test-token",
+ }
+ migration_entity = {
+ "source_full_path": "source",
+ "source_type": "group_entity",
+ "destination_slug": "destination",
+ "destination_namespace": "destination",
+ }
+ migration = gl.bulk_imports.create(
+ {
+ "configuration": configuration,
+ "entities": [migration_entity],
+ }
+ )
+ assert isinstance(migration, BulkImport)
+ assert migration.status == "finished"
+
+
+def test_list_bulk_imports(gl, resp_list_bulk_imports):
+ migrations = gl.bulk_imports.list()
+ assert isinstance(migrations[0], BulkImport)
+ assert migrations[0].status == "finished"
+
+
+def test_get_bulk_import(gl, resp_get_bulk_import):
+ migration = gl.bulk_imports.get(1)
+ assert isinstance(migration, BulkImport)
+ assert migration.status == "finished"
+
+
+def test_list_all_bulk_import_entities(gl, resp_list_all_bulk_import_entities):
+ entities = gl.bulk_import_entities.list()
+ assert isinstance(entities[0], BulkImportAllEntity)
+ assert entities[0].bulk_import_id == 1
+
+
+def test_list_bulk_import_entities(gl, migration, resp_list_bulk_import_entities):
+ entities = migration.entities.list()
+ assert isinstance(entities[0], BulkImportEntity)
+ assert entities[0].bulk_import_id == 1
+
+
+def test_get_bulk_import_entity(gl, migration, resp_get_bulk_import_entity):
+ entity = migration.entities.get(1)
+ assert isinstance(entity, BulkImportEntity)
+ assert entity.bulk_import_id == 1