From 19bb56d28db7e2287b62216a7cff2a70baddcc40 Mon Sep 17 00:00:00 2001 From: Christian Sattler Date: Wed, 5 Jan 2022 18:00:45 +0100 Subject: [PATCH 1/3] feat: add support for Groups API method `transfer()` --- gitlab/exceptions.py | 4 ++++ gitlab/v4/objects/groups.py | 24 +++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py index 6b8647152..54f9b8cd0 100644 --- a/gitlab/exceptions.py +++ b/gitlab/exceptions.py @@ -107,6 +107,10 @@ class GitlabTransferProjectError(GitlabOperationError): pass +class GitlabGroupTransferError(GitlabOperationError): + pass + + class GitlabProjectDeployKeyError(GitlabOperationError): pass diff --git a/gitlab/v4/objects/groups.py b/gitlab/v4/objects/groups.py index 453548b94..be26e39ce 100644 --- a/gitlab/v4/objects/groups.py +++ b/gitlab/v4/objects/groups.py @@ -95,12 +95,34 @@ def transfer_project(self, project_id: int, **kwargs: Any) -> None: path = f"/groups/{self.id}/projects/{project_id}" self.manager.gitlab.http_post(path, **kwargs) + @cli.register_custom_action("Group", tuple(), ("group_id",)) + @exc.on_http_error(exc.GitlabGroupTransferError) + def transfer(self, group_id: Optional[int] = None, **kwargs: Any) -> None: + """Transfer the group to a new parent group or make it a top-level group. + + Requires GitLab ≥14.6. + + Args: + group_id: ID of the new parent group. When not specified, + the group to transfer is instead turned into a top-level group. + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabGroupTransferError: If the group could not be transferred + """ + path = f"/groups/{self.id}/transfer" + post_data = {} + if group_id is not None: + post_data["group_id"] = group_id + self.manager.gitlab.http_post(path, post_data=post_data, **kwargs) + @cli.register_custom_action("Group", ("scope", "search")) @exc.on_http_error(exc.GitlabSearchError) def search( self, scope: str, search: str, **kwargs: Any ) -> Union[gitlab.GitlabList, List[Dict[str, Any]]]: - """Search the group resources matching the provided string.' + """Search the group resources matching the provided string. Args: scope: Scope of the search From 66f71021bf51066d5c3eee8abb495abe7a8d2521 Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Fri, 14 Jan 2022 01:12:47 +0100 Subject: [PATCH 2/3] chore(groups): use encoded_id for group path --- gitlab/v4/objects/groups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/v4/objects/groups.py b/gitlab/v4/objects/groups.py index be26e39ce..933f48c64 100644 --- a/gitlab/v4/objects/groups.py +++ b/gitlab/v4/objects/groups.py @@ -111,7 +111,7 @@ def transfer(self, group_id: Optional[int] = None, **kwargs: Any) -> None: GitlabAuthenticationError: If authentication is not correct GitlabGroupTransferError: If the group could not be transferred """ - path = f"/groups/{self.id}/transfer" + path = f"/groups/{self.encoded_id}/transfer" post_data = {} if group_id is not None: post_data["group_id"] = group_id From fd29c0a4c291579f7d5ff2f3c99fbcb1401dab70 Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Fri, 14 Jan 2022 01:49:27 +0100 Subject: [PATCH 3/3] test(groups): enable group transfer tests --- tests/functional/api/test_groups.py | 14 ++++++++------ tests/functional/fixtures/.env | 2 +- tests/unit/objects/test_groups.py | 5 ++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/functional/api/test_groups.py b/tests/functional/api/test_groups.py index 584ea8355..b61305569 100644 --- a/tests/functional/api/test_groups.py +++ b/tests/functional/api/test_groups.py @@ -233,17 +233,19 @@ def test_group_hooks(group): hook.delete() -@pytest.mark.skip(reason="Pending #1807") def test_group_transfer(gl, group): - transfer_group = gl.groups.create({"name": "transfer-test-group"}) - assert group.namespace["path"] != group.full_path + transfer_group = gl.groups.create( + {"name": "transfer-test-group", "path": "transfer-test-group"} + ) + transfer_group = gl.groups.get(transfer_group.id) + assert transfer_group.parent_id != group.id transfer_group.transfer(group.id) - transferred_group = gl.projects.get(transfer_group.id) - assert transferred_group.namespace["path"] == group.full_path + transferred_group = gl.groups.get(transfer_group.id) + assert transferred_group.parent_id == group.id transfer_group.transfer() - transferred_group = gl.projects.get(transfer_group.id) + transferred_group = gl.groups.get(transfer_group.id) assert transferred_group.path == transferred_group.full_path diff --git a/tests/functional/fixtures/.env b/tests/functional/fixtures/.env index 30abd5caf..bcfd35713 100644 --- a/tests/functional/fixtures/.env +++ b/tests/functional/fixtures/.env @@ -1,2 +1,2 @@ GITLAB_IMAGE=gitlab/gitlab-ce -GITLAB_TAG=14.5.2-ce.0 +GITLAB_TAG=14.6.2-ce.0 diff --git a/tests/unit/objects/test_groups.py b/tests/unit/objects/test_groups.py index b3e753e4b..2c91d38d8 100644 --- a/tests/unit/objects/test_groups.py +++ b/tests/unit/objects/test_groups.py @@ -99,13 +99,13 @@ def resp_create_import(accepted_content): def resp_transfer_group(): with responses.RequestsMock() as rsps: rsps.add( - method=responses.PUT, + method=responses.POST, url="http://localhost/api/v4/groups/1/transfer", json=content, content_type="application/json", status=200, match=[ - responses.matchers.json_params_matcher({"namespace": "test-namespace"}) + responses.matchers.json_params_matcher({"group_id": "test-namespace"}) ], ) yield rsps @@ -170,7 +170,6 @@ def test_refresh_group_import_status(group, resp_groups): assert group_import.import_status == "finished" -@pytest.mark.skip("Pending #1807") def test_transfer_group(gl, resp_transfer_group): group = gl.groups.get(1, lazy=True) group.transfer("test-namespace")