Repository: python-gitlab/python-gitlab

Issue:
Support User Runner Registration
## Description of the problem, including code/CLI snippet

As part of Gitlab's runner registration changes, they have introduced a new [runner registration api](https://docs.gitlab.com/ee/api/users.html#create-a-runner) tied to the authenticated current user. This is similar to the group/project runner create (which are being deprecated), except that the registration token is no longer used.

Runner registration tokens will be removed in 16.6.

## Expected Behavior


## Actual Behavior


## Specifications

  - python-gitlab version:
  - API version you are using (v3/v4):
  - Gitlab server version (or gitlab.com): 16+



Discussion:

Resolving patch:
diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py
--- a/gitlab/v4/objects/users.py
+++ b/gitlab/v4/objects/users.py
@@ -36,6 +36,8 @@
     "CurrentUserGPGKeyManager",
     "CurrentUserKey",
     "CurrentUserKeyManager",
+    "CurrentUserRunner",
+    "CurrentUserRunnerManager",
     "CurrentUserStatus",
     "CurrentUserStatusManager",
     "CurrentUser",
@@ -111,6 +113,31 @@ def get(
         return cast(CurrentUserKey, super().get(id=id, lazy=lazy, **kwargs))
 
 
+class CurrentUserRunner(RESTObject):
+    pass
+
+
+class CurrentUserRunnerManager(CreateMixin, RESTManager):
+    _path = "/user/runners"
+    _obj_cls = CurrentUserRunner
+    _types = {"tag_list": types.CommaSeparatedListAttribute}
+    _create_attrs = RequiredOptional(
+        required=("runner_type",),
+        optional=(
+            "group_id",
+            "project_id",
+            "description",
+            "paused",
+            "locked",
+            "run_untagged",
+            "tag_list",
+            "access_level",
+            "maximum_timeout",
+            "maintenance_note",
+        ),
+    )
+
+
 class CurrentUserStatus(SaveMixin, RESTObject):
     _id_attr = None
     _repr_attr = "message"
@@ -132,6 +159,7 @@ class CurrentUser(RESTObject):
     emails: CurrentUserEmailManager
     gpgkeys: CurrentUserGPGKeyManager
     keys: CurrentUserKeyManager
+    runners: CurrentUserRunnerManager
     status: CurrentUserStatusManager
 
 


Test patch:
diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py
--- a/tests/unit/conftest.py
+++ b/tests/unit/conftest.py
@@ -1,4 +1,5 @@
 import pytest
+import responses
 
 import gitlab
 from tests.unit import helpers
@@ -59,6 +60,23 @@ def gl_retry():
     )
 
 
+@pytest.fixture
+def resp_get_current_user():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.GET,
+            url="http://localhost/api/v4/user",
+            json={
+                "id": 1,
+                "username": "username",
+                "web_url": "http://localhost/username",
+            },
+            content_type="application/json",
+            status=200,
+        )
+        yield rsps
+
+
 # Todo: parametrize, but check what tests it's really useful for
 @pytest.fixture
 def gl_trailing():
@@ -129,6 +147,12 @@ def user(gl):
     return gl.users.get(1, lazy=True)
 
 
+@pytest.fixture
+def current_user(gl, resp_get_current_user):
+    gl.auth()
+    return gl.user
+
+
 @pytest.fixture
 def migration(gl):
     return gl.bulk_imports.get(1, lazy=True)
diff --git a/tests/unit/objects/test_users.py b/tests/unit/objects/test_users.py
--- a/tests/unit/objects/test_users.py
+++ b/tests/unit/objects/test_users.py
@@ -241,6 +241,19 @@ def resp_starred_projects():
         yield rsps
 
 
+@pytest.fixture
+def resp_runner_create():
+    with responses.RequestsMock() as rsps:
+        rsps.add(
+            method=responses.POST,
+            url="http://localhost/api/v4/user/runners",
+            json={"id": "6", "token": "6337ff461c94fd3fa32ba3b1ff4125"},
+            content_type="application/json",
+            status=201,
+        )
+        yield rsps
+
+
 def test_get_user(gl, resp_get_user):
     user = gl.users.get(1)
     assert isinstance(user, User)
@@ -304,3 +317,9 @@ def test_list_starred_projects(user, resp_starred_projects):
     projects = user.starred_projects.list()
     assert isinstance(projects[0], StarredProject)
     assert projects[0].id == project_content["id"]
+
+
+def test_create_user_runner(current_user, resp_runner_create):
+    runner = current_user.runners.create({"runner_type": "instance_type"})
+    assert runner.id == "6"
+    assert runner.token == "6337ff461c94fd3fa32ba3b1ff4125"