diff --git a/docs/gl_objects/runners.rst b/docs/gl_objects/runners.rst index 10bdba245..113ce2585 100644 --- a/docs/gl_objects/runners.rst +++ b/docs/gl_objects/runners.rst @@ -71,6 +71,14 @@ Register a new runner:: runner = gl.runners.create({'token': secret_token}) +.. note:: + + A new runner registration workflow has been introduced since GitLab 16.0. This new + workflow comes with a new API endpoint to create runners, which does not use + registration tokens. + + The new endpoint can be called using ``gl.user.runners.create() after authenticating with `gl.auth()```. + Update a runner:: runner = gl.runners.get(runner_id) diff --git a/docs/gl_objects/users.rst b/docs/gl_objects/users.rst index 37354e278..a6278d231 100644 --- a/docs/gl_objects/users.rst +++ b/docs/gl_objects/users.rst @@ -456,3 +456,60 @@ Get the users activities:: query_parameters={'from': '2018-07-01'}, get_all=True, ) + +Create new runner +================= + +References +---------- + +* New runner registration API endpoint (see `Migrating to the new runner registration workflow `_) + +* v4 API: + + + :class:`gitlab.v4.objects.CurrentUserRunner` + + :class:`gitlab.v4.objects.CurrentUserRunnerManager` + + :attr:`gitlab.Gitlab.user.runners` + +* GitLab API : https://docs.gitlab.com/ee/api/users.html#create-a-runner + +Examples +-------- + +Create an instance-wide runner:: + + runner = gl.user.runners.create({ + "runner_type": "instance_type", + "description": "My brand new runner", + "paused": True, + "locked": False, + "run_untagged": True + "tag_list": ["linux", "docker", "testing"], + "access_level": "not_protected" + }) + +Create a group runner:: + + runner = gl.user.runners.create({ + "runner_type": "group_type", + "group_id": 12345678, + "description": "My brand new runner", + "paused": True, + "locked": False, + "run_untagged": True + "tag_list": ["linux", "docker", "testing"], + "access_level": "not_protected" + }) + +Create a project runner:: + + runner = gl.user.runners.create({ + "runner_type": "project_type", + "project_id": 987564321, + "description": "My brand new runner", + "paused": True, + "locked": False, + "run_untagged": True + "tag_list": ["linux", "docker", "testing"], + "access_level": "not_protected" + }) \ No newline at end of file diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py index 7395313c8..e2f10574e 100644 --- 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 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index d72637196..bbfd4c230 100644 --- 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 index 6da68adec..0bbcc7637 100644 --- 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"