summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGauvain Pocentek <gauvain@pocentek.net>2018-05-21 15:06:44 +0200
committerGauvain Pocentek <gauvain@pocentek.net>2018-05-21 15:06:44 +0200
commitb5f9616f21b7dcdf166033d0dba09b3dd2289849 (patch)
tree6e12c39667ea59b11625d1875bf008bc542f4ef1
parent42007ec651e6203f608484e6de899907196a808f (diff)
downloadgitlab-b5f9616f21b7dcdf166033d0dba09b3dd2289849.tar.gz
Add support for project import/export
Fixes #471
-rw-r--r--docs/gl_objects/projects.rst57
-rw-r--r--gitlab/mixins.py5
-rw-r--r--gitlab/v4/objects.py84
-rw-r--r--tools/python_test_v4.py24
4 files changed, 169 insertions, 1 deletions
diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst
index fdea7aa..6d69295 100644
--- a/docs/gl_objects/projects.rst
+++ b/docs/gl_objects/projects.rst
@@ -180,6 +180,63 @@ Get a list of users for the repository::
# search for users
users = p.users.list(search='pattern')
+Import / Export
+===============
+
+You can export projects from gitlab, and re-import them to create new projects
+or overwrite existing ones.
+
+Reference
+---------
+
+* v4 API:
+
+ + :class:`gitlab.v4.objects.ProjectExport`
+ + :class:`gitlab.v4.objects.ProjectExportManager`
+ + :attr:`gitlab.v4.objects.Project.exports`
+ + :class:`gitlab.v4.objects.ProjectImport`
+ + :class:`gitlab.v4.objects.ProjectImportManager`
+ + :attr:`gitlab.v4.objects.Project.imports`
+ + :attr:`gitlab.v4.objects.ProjectManager.import_project`
+
+* GitLab API: https://docs.gitlab.com/ce/api/project_import_export.html
+
+Examples
+--------
+
+A project export is an asynchronous operation. To retrieve the archive
+generated by GitLab you need to:
+
+#. Create an export using the API
+#. Wait for the export to be done
+#. Download the result
+
+::
+
+ # Create the export
+ p = gl.projects.get(my_project)
+ export = p.exports.create({})
+
+ # Wait for the 'finished' status
+ export.refresh()
+ while export.export_status != 'finished':
+ time.sleep(1)
+ export.refresh()
+
+ # Download the result
+ with open('/tmp/export.tgz', 'wb') as f:
+ export.download(streamed=True, action=f.write)
+
+Import the project::
+
+ gl.projects.import_project(open('/tmp/export.tgz', 'rb'), 'my_new_project')
+ # Get a ProjectImport object to track the import status
+ project_import = gl.projects.get(output['id'], lazy=True).imports.get()
+ while project_import.import_status != 'finished':
+ time.sleep(1)
+ project_import.refresh()
+
+
Project custom attributes
=========================
diff --git a/gitlab/mixins.py b/gitlab/mixins.py
index 810a37b..581e3d5 100644
--- a/gitlab/mixins.py
+++ b/gitlab/mixins.py
@@ -83,7 +83,10 @@ class RefreshMixin(object):
GitlabAuthenticationError: If authentication is not correct
GitlabGetError: If the server cannot perform the request
"""
- path = '%s/%s' % (self.manager.path, self.id)
+ if self._id_attr:
+ path = '%s/%s' % (self.manager.path, self.id)
+ else:
+ path = self.manager.path
server_data = self.manager.gitlab.http_get(path, **kwargs)
self._update_attrs(server_data)
diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py
index 2d9a6bf..ac25f1e 100644
--- a/gitlab/v4/objects.py
+++ b/gitlab/v4/objects.py
@@ -2400,6 +2400,53 @@ class ProjectWikiManager(CRUDMixin, RESTManager):
_list_filters = ('with_content', )
+class ProjectExport(RefreshMixin, RESTObject):
+ _id_attr = None
+
+ @cli.register_custom_action('ProjectExport')
+ @exc.on_http_error(exc.GitlabGetError)
+ def download(self, streamed=False, action=None, chunk_size=1024, **kwargs):
+ """Download the archive of a project export.
+
+ Args:
+ streamed (bool): If True the data will be processed by chunks of
+ `chunk_size` and each chunk is passed to `action` for
+ reatment
+ action (callable): Callable responsible of dealing with chunk of
+ data
+ chunk_size (int): Size of each chunk
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabGetError: If the server failed to perform the request
+
+ Returns:
+ str: The blob content if streamed is False, None otherwise
+ """
+ path = '/projects/%d/export/download' % self.project_id
+ result = self.manager.gitlab.http_get(path, streamed=streamed,
+ **kwargs)
+ return utils.response_content(result, streamed, action, chunk_size)
+
+
+class ProjectExportManager(GetWithoutIdMixin, CreateMixin, RESTManager):
+ _path = '/projects/%(project_id)s/export'
+ _obj_cls = ProjectExport
+ _from_parent_attrs = {'project_id': 'id'}
+ _create_attrs = (tuple(), ('description',))
+
+
+class ProjectImport(RefreshMixin, RESTObject):
+ _id_attr = None
+
+
+class ProjectImportManager(GetWithoutIdMixin, RESTManager):
+ _path = '/projects/%(project_id)s/import'
+ _obj_cls = ProjectImport
+ _from_parent_attrs = {'project_id': 'id'}
+
+
class Project(SaveMixin, ObjectDeleteMixin, RESTObject):
_short_print_attr = 'path'
_managers = (
@@ -2412,10 +2459,12 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject):
('deployments', 'ProjectDeploymentManager'),
('environments', 'ProjectEnvironmentManager'),
('events', 'ProjectEventManager'),
+ ('exports', 'ProjectExportManager'),
('files', 'ProjectFileManager'),
('forks', 'ProjectForkManager'),
('hooks', 'ProjectHookManager'),
('keys', 'ProjectKeyManager'),
+ ('imports', 'ProjectImportManager'),
('issues', 'ProjectIssueManager'),
('labels', 'ProjectLabelManager'),
('members', 'ProjectMemberManager'),
@@ -2847,6 +2896,41 @@ class ProjectManager(CRUDMixin, RESTManager):
'with_issues_enabled', 'with_merge_requests_enabled',
'custom_attributes')
+ def import_project(self, file, path, namespace=None, overwrite=False,
+ override_params=None, **kwargs):
+ """Import a project from an archive file.
+
+ Args:
+ file: Data or file object containing the project
+ path (str): Name and path for the new project
+ namespace (str): The ID or path of the namespace that the project
+ will be imported to
+ overwrite (bool): If True overwrite an existing project with the
+ same path
+ override_params (dict): Set the specific settings for the project
+ **kwargs: Extra options to send to the server (e.g. sudo)
+
+ Raises:
+ GitlabAuthenticationError: If authentication is not correct
+ GitlabListError: If the server failed to perform the request
+
+ Returns:
+ dict: A representation of the import status.
+ """
+ files = {
+ 'file': ('file.tar.gz', file)
+ }
+ data = {
+ 'path': path,
+ 'overwrite': overwrite
+ }
+ if override_params:
+ data['override_params'] = override_params
+ if namespace:
+ data['namespace'] = namespace
+ return self.gitlab.http_post('/projects/import', post_data=data,
+ files=files, **kwargs)
+
class Runner(SaveMixin, ObjectDeleteMixin, RESTObject):
pass
diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py
index c11e567..01de5bd 100644
--- a/tools/python_test_v4.py
+++ b/tools/python_test_v4.py
@@ -678,3 +678,27 @@ assert 'Retry later' in error_message
[current_project.delete() for current_project in projects]
settings.throttle_authenticated_api_enabled = False
settings.save()
+
+# project import/export
+ex = admin_project.exports.create({})
+ex.refresh()
+count = 0
+while ex.export_status != 'finished':
+ time.sleep(1)
+ ex.refresh()
+ count += 1
+ if count == 10:
+ raise Exception('Project export taking too much time')
+with open('/tmp/gitlab-export.tgz', 'wb') as f:
+ ex.download(streamed=True, action=f.write)
+
+output = gl.projects.import_project(open('/tmp/gitlab-export.tgz', 'rb'),
+ 'imported_project')
+project_import = gl.projects.get(output['id'], lazy=True).imports.get()
+count = 0
+while project_import.import_status != 'finished':
+ time.sleep(1)
+ project_import.refresh()
+ count += 1
+ if count == 10:
+ raise Exception('Project import taking too much time')