diff options
author | Gauvain Pocentek <gauvain@pocentek.net> | 2018-05-21 15:06:44 +0200 |
---|---|---|
committer | Gauvain Pocentek <gauvain@pocentek.net> | 2018-05-21 15:06:44 +0200 |
commit | b5f9616f21b7dcdf166033d0dba09b3dd2289849 (patch) | |
tree | 6e12c39667ea59b11625d1875bf008bc542f4ef1 | |
parent | 42007ec651e6203f608484e6de899907196a808f (diff) | |
download | gitlab-b5f9616f21b7dcdf166033d0dba09b3dd2289849.tar.gz |
Add support for project import/export
Fixes #471
-rw-r--r-- | docs/gl_objects/projects.rst | 57 | ||||
-rw-r--r-- | gitlab/mixins.py | 5 | ||||
-rw-r--r-- | gitlab/v4/objects.py | 84 | ||||
-rw-r--r-- | tools/python_test_v4.py | 24 |
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') |