Repository: python-gitlab/python-gitlab

Issue:
Support for project remote mirrors API
Just reading the 12.9 release notes and see that there is now an API for managing HTTP push mirrors:

https://docs.gitlab.com/ee/api/remote_mirrors.html


Discussion:

Resolving patch:
diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py
--- a/gitlab/v4/objects.py
+++ b/gitlab/v4/objects.py
@@ -1745,6 +1745,18 @@ def delete_in_bulk(self, name_regex=".*", **kwargs):
         self.gitlab.http_delete(self.path, query_data=data, **kwargs)
 
 
+class ProjectRemoteMirror(SaveMixin, RESTObject):
+    pass
+
+
+class ProjectRemoteMirrorManager(ListMixin, CreateMixin, UpdateMixin, RESTManager):
+    _path = "/projects/%(project_id)s/remote_mirrors"
+    _obj_cls = ProjectRemoteMirror
+    _from_parent_attrs = {"project_id": "id"}
+    _create_attrs = (("url",), ("enabled", "only_protected_branches"))
+    _update_attrs = (tuple(), ("enabled", "only_protected_branches"))
+
+
 class ProjectBoardList(SaveMixin, ObjectDeleteMixin, RESTObject):
     pass
 
@@ -4246,6 +4258,7 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject):
         ("pipelineschedules", "ProjectPipelineScheduleManager"),
         ("pushrules", "ProjectPushRulesManager"),
         ("releases", "ProjectReleaseManager"),
+        ("remote_mirrors", "ProjectRemoteMirrorManager"),
         ("repositories", "ProjectRegistryRepositoryManager"),
         ("runners", "ProjectRunnerManager"),
         ("services", "ProjectServiceManager"),


Test patch:
diff --git a/gitlab/tests/objects/test_projects.py b/gitlab/tests/objects/test_projects.py
--- a/gitlab/tests/objects/test_projects.py
+++ b/gitlab/tests/objects/test_projects.py
@@ -90,6 +90,77 @@ def resp_import_github(url, request):
     return response(200, content, headers, None, 25, request)
 
 
+@urlmatch(
+    scheme="http",
+    netloc="localhost",
+    path="/api/v4/projects/1/remote_mirrors",
+    method="get",
+)
+def resp_get_remote_mirrors(url, request):
+    """Mock for Project Remote Mirrors GET response."""
+    content = """[
+      {
+        "enabled": true,
+        "id": 101486,
+        "last_error": null,
+        "last_successful_update_at": "2020-01-06T17:32:02.823Z",
+        "last_update_at": "2020-01-06T17:32:02.823Z",
+        "last_update_started_at": "2020-01-06T17:31:55.864Z",
+        "only_protected_branches": true,
+        "update_status": "finished",
+        "url": "https://*****:*****@gitlab.com/gitlab-org/security/gitlab.git"
+      }
+    ]"""
+    content = content.encode("utf-8")
+    return response(200, content, headers, None, 5, request)
+
+
+@urlmatch(
+    scheme="http",
+    netloc="localhost",
+    path="/api/v4/projects/1/remote_mirrors",
+    method="post",
+)
+def resp_create_remote_mirror(url, request):
+    """Mock for Project Remote Mirrors POST response."""
+    content = """{
+        "enabled": false,
+        "id": 101486,
+        "last_error": null,
+        "last_successful_update_at": null,
+        "last_update_at": null,
+        "last_update_started_at": null,
+        "only_protected_branches": false,
+        "update_status": "none",
+        "url": "https://*****:*****@example.com/gitlab/example.git"
+    }"""
+    content = content.encode("utf-8")
+    return response(200, content, headers, None, 5, request)
+
+
+@urlmatch(
+    scheme="http",
+    netloc="localhost",
+    path="/api/v4/projects/1/remote_mirrors/1",
+    method="put",
+)
+def resp_update_remote_mirror(url, request):
+    """Mock for Project Remote Mirrors PUT response."""
+    content = """{
+        "enabled": false,
+        "id": 101486,
+        "last_error": null,
+        "last_successful_update_at": "2020-01-06T17:32:02.823Z",
+        "last_update_at": "2020-01-06T17:32:02.823Z",
+        "last_update_started_at": "2020-01-06T17:31:55.864Z",
+        "only_protected_branches": true,
+        "update_status": "finished",
+        "url": "https://*****:*****@gitlab.com/gitlab-org/security/gitlab.git"
+    }"""
+    content = content.encode("utf-8")
+    return response(200, content, headers, None, 5, request)
+
+
 class TestProject(unittest.TestCase):
     """Base class for GitLab Project tests."""
 
@@ -262,3 +333,26 @@ def test_import_github(self):
         self.assertEqual(ret["name"], name)
         self.assertEqual(ret["full_path"], "/".join((base_path, name)))
         self.assertTrue(ret["full_name"].endswith(name))
+
+
+class TestProjectRemoteMirrors(TestProject):
+    @with_httmock(resp_get_remote_mirrors)
+    def test_list_project_remote_mirrors(self):
+        mirrors = self.project.remote_mirrors.list()
+        self.assertIsInstance(mirrors, list)
+        self.assertIsInstance(mirrors[0], ProjectRemoteMirror)
+        self.assertTrue(mirrors[0].enabled)
+
+    @with_httmock(resp_create_remote_mirror)
+    def test_create_project_remote_mirror(self):
+        mirror = self.project.remote_mirrors.create({"url": "https://example.com"})
+        self.assertIsInstance(mirror, ProjectRemoteMirror)
+        self.assertEqual(mirror.update_status, "none")
+
+    @with_httmock(resp_create_remote_mirror, resp_update_remote_mirror)
+    def test_update_project_remote_mirror(self):
+        mirror = self.project.remote_mirrors.create({"url": "https://example.com"})
+        mirror.only_protected_branches = True
+        mirror.save()
+        self.assertEqual(mirror.update_status, "finished")
+        self.assertTrue(mirror.only_protected_branches)
diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py
--- a/tools/python_test_v4.py
+++ b/tools/python_test_v4.py
@@ -1047,6 +1047,23 @@
 assert len(release_test_project.releases.list()) == 0
 release_test_project.delete()
 
+# project remote mirrors
+mirror_url = "http://gitlab.test/root/mirror.git"
+
+# create remote mirror
+mirror = admin_project.remote_mirrors.create({"url": mirror_url})
+assert mirror.url == mirror_url
+
+# update remote mirror
+mirror.enabled = True
+mirror.save()
+
+# list remote mirrors
+mirror = admin_project.remote_mirrors.list()[0]
+assert isinstance(mirror, gitlab.v4.objects.ProjectRemoteMirror)
+assert mirror.url == mirror_url
+assert mirror.enabled is True
+
 # status
 message = "Test"
 emoji = "thumbsup"