From 49439916ab58b3481308df5800f9ffba8f5a8ffd Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Mon, 4 May 2020 08:10:49 +0200 Subject: [PATCH 1/2] feat: add group runners api --- docs/gl_objects/runners.rst | 5 ++++- gitlab/v4/objects.py | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/gl_objects/runners.rst b/docs/gl_objects/runners.rst index ceda32a2f..b369bedb5 100644 --- a/docs/gl_objects/runners.rst +++ b/docs/gl_objects/runners.rst @@ -78,7 +78,7 @@ Verify a registered runner token:: except GitlabVerifyError: print("Invalid token") -Project runners +Project/Group runners =============== Reference @@ -89,6 +89,9 @@ Reference + :class:`gitlab.v4.objects.ProjectRunner` + :class:`gitlab.v4.objects.ProjectRunnerManager` + :attr:`gitlab.v4.objects.Project.runners` + + :class:`gitlab.v4.objects.GroupRunner` + + :class:`gitlab.v4.objects.GroupRunnerManager` + + :attr:`gitlab.v4.objects.Group.runners` * GitLab API: https://docs.gitlab.com/ce/api/runners.html diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 42b2bf48c..bc45bf1a9 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -1308,6 +1308,17 @@ class GroupProjectManager(ListMixin, RESTManager): ) +class GroupRunner(ObjectDeleteMixin, RESTObject): + pass + + +class GroupRunnerManager(NoUpdateMixin, RESTManager): + _path = "/groups/%(group_id)s/runners" + _obj_cls = GroupRunner + _from_parent_attrs = {"group_id": "id"} + _create_attrs = (("runner_id",), tuple()) + + class GroupSubgroup(RESTObject): pass @@ -1357,6 +1368,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject): ("milestones", "GroupMilestoneManager"), ("notificationsettings", "GroupNotificationSettingsManager"), ("projects", "GroupProjectManager"), + ("runners", "GroupRunnerManager"), ("subgroups", "GroupSubgroupManager"), ("variables", "GroupVariableManager"), ("clusters", "GroupClusterManager"), @@ -5382,7 +5394,8 @@ def all(self, scope=None, **kwargs): query_data = {} if scope is not None: query_data["scope"] = scope - return self.gitlab.http_list(path, query_data, **kwargs) + obj = self.gitlab.http_list(path, query_data, **kwargs) + return [self._obj_cls(self, item) for item in obj] @cli.register_custom_action("RunnerManager", ("token",)) @exc.on_http_error(exc.GitlabVerifyError) From 127fa5a2134aee82958ce05357d60513569c3659 Mon Sep 17 00:00:00 2001 From: Max Wittig Date: Sat, 16 May 2020 14:41:56 +0200 Subject: [PATCH 2/2] test(runners): add all runners unit tests --- README.rst | 5 + gitlab/tests/conftest.py | 12 ++ gitlab/tests/objects/test_runners.py | 277 +++++++++++++++++++++++++++ test-requirements.txt | 1 + 4 files changed, 295 insertions(+) create mode 100644 gitlab/tests/conftest.py create mode 100644 gitlab/tests/objects/test_runners.py diff --git a/README.rst b/README.rst index 7aa090499..d8a035804 100644 --- a/README.rst +++ b/README.rst @@ -128,6 +128,11 @@ Before submitting a pull request make sure that the tests still succeed with your change. Unit tests and functional tests run using the travis service and passing tests are mandatory to get merge requests accepted. +We're currently in a restructing phase for the unit tests. If you're changing existing +tests, feel free to keep the current format. Otherwise please write new tests with pytest and +using `responses`_. An example for new tests can be found in +tests/objects/test_runner.py + You need to install ``tox`` to run unit tests and documentation builds locally: .. code-block:: bash diff --git a/gitlab/tests/conftest.py b/gitlab/tests/conftest.py new file mode 100644 index 000000000..91752c671 --- /dev/null +++ b/gitlab/tests/conftest.py @@ -0,0 +1,12 @@ +import pytest +import gitlab + + +@pytest.fixture +def gl(): + return gitlab.Gitlab( + "http://localhost", + private_token="private_token", + ssl_verify=True, + api_version=4, + ) diff --git a/gitlab/tests/objects/test_runners.py b/gitlab/tests/objects/test_runners.py new file mode 100644 index 000000000..2f86bef8d --- /dev/null +++ b/gitlab/tests/objects/test_runners.py @@ -0,0 +1,277 @@ +import unittest +import responses +import gitlab +import pytest +import re +from .mocks import * # noqa + + +runner_detail = { + "active": True, + "architecture": "amd64", + "description": "test-1-20150125", + "id": 6, + "ip_address": "127.0.0.1", + "is_shared": False, + "contacted_at": "2016-01-25T16:39:48.066Z", + "name": "test-runner", + "online": True, + "status": "online", + "platform": "linux", + "projects": [ + { + "id": 1, + "name": "GitLab Community Edition", + "name_with_namespace": "GitLab.org / GitLab Community Edition", + "path": "gitlab-foss", + "path_with_namespace": "gitlab-org/gitlab-foss", + } + ], + "revision": "5nj35", + "tag_list": ["ruby", "mysql"], + "version": "v13.0.0", + "access_level": "ref_protected", + "maximum_timeout": 3600, +} + +runner_shortinfo = { + "active": True, + "description": "test-1-20150125", + "id": 6, + "is_shared": False, + "ip_address": "127.0.0.1", + "name": "test-name", + "online": True, + "status": "online", +} + +runner_jobs = [ + { + "id": 6, + "ip_address": "127.0.0.1", + "status": "running", + "stage": "test", + "name": "test", + "ref": "master", + "tag": False, + "coverage": "99%", + "created_at": "2017-11-16T08:50:29.000Z", + "started_at": "2017-11-16T08:51:29.000Z", + "finished_at": "2017-11-16T08:53:29.000Z", + "duration": 120, + "user": { + "id": 1, + "name": "John Doe2", + "username": "user2", + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon", + "web_url": "http://localhost/user2", + "created_at": "2017-11-16T18:38:46.000Z", + "bio": None, + "location": None, + "public_email": "", + "skype": "", + "linkedin": "", + "twitter": "", + "website_url": "", + "organization": None, + }, + } +] + + +@pytest.fixture +def resp_get_runners_jobs(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.GET, + url="http://localhost/api/v4/runners/6/jobs", + json=runner_jobs, + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_get_runners_list(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.GET, + url=re.compile(r".*?(/runners(/all)?|/(groups|projects)/1/runners)"), + json=[runner_shortinfo], + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_runner_detail(): + with responses.RequestsMock() as rsps: + pattern = re.compile(r".*?/runners/6") + rsps.add( + method=responses.GET, + url=pattern, + json=runner_detail, + content_type="application/json", + status=200, + ) + rsps.add( + method=responses.PUT, + url=pattern, + json=runner_detail, + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_runner_register(): + with responses.RequestsMock() as rsps: + pattern = re.compile(r".*?/runners") + rsps.add( + method=responses.POST, + url=pattern, + json={"id": "6", "token": "6337ff461c94fd3fa32ba3b1ff4125"}, + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_runner_enable(): + with responses.RequestsMock() as rsps: + pattern = re.compile(r".*?(projects|groups)/1/runners") + rsps.add( + method=responses.POST, + url=pattern, + json=runner_shortinfo, + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_runner_delete(): + with responses.RequestsMock() as rsps: + pattern = re.compile(r".*?/runners/6") + rsps.add( + method=responses.GET, + url=pattern, + json=runner_detail, + content_type="application/json", + status=200, + ) + rsps.add( + method=responses.DELETE, url=pattern, status=204, + ) + yield rsps + + +@pytest.fixture +def resp_runner_disable(): + with responses.RequestsMock() as rsps: + pattern = re.compile(r".*?/(groups|projects)/1/runners/6") + rsps.add( + method=responses.DELETE, url=pattern, status=204, + ) + yield rsps + + +@pytest.fixture +def resp_runner_verify(): + with responses.RequestsMock() as rsps: + pattern = re.compile(r".*?/runners/verify") + rsps.add( + method=responses.POST, url=pattern, status=200, + ) + yield rsps + + +def test_owned_runners_list(gl: gitlab.Gitlab, resp_get_runners_list): + runners = gl.runners.list() + assert runners[0].active == True + assert runners[0].id == 6 + assert runners[0].name == "test-name" + assert len(runners) == 1 + + +def test_project_runners_list(gl: gitlab.Gitlab, resp_get_runners_list): + runners = gl.projects.get(1, lazy=True).runners.list() + assert runners[0].active == True + assert runners[0].id == 6 + assert runners[0].name == "test-name" + assert len(runners) == 1 + + +def test_group_runners_list(gl: gitlab.Gitlab, resp_get_runners_list): + runners = gl.groups.get(1, lazy=True).runners.list() + assert runners[0].active == True + assert runners[0].id == 6 + assert runners[0].name == "test-name" + assert len(runners) == 1 + + +def test_all_runners_list(gl: gitlab.Gitlab, resp_get_runners_list): + runners = gl.runners.all() + assert runners[0].active == True + assert runners[0].id == 6 + assert runners[0].name == "test-name" + assert len(runners) == 1 + + +def test_create_runner(gl: gitlab.Gitlab, resp_runner_register): + runner = gl.runners.create({"token": "token"}) + assert runner.id == "6" + assert runner.token == "6337ff461c94fd3fa32ba3b1ff4125" + + +def test_get_update_runner(gl: gitlab.Gitlab, resp_runner_detail): + runner = gl.runners.get(6) + assert runner.active == True + runner.tag_list.append("new") + runner.save() + + +def test_remove_runner(gl: gitlab.Gitlab, resp_runner_delete): + runner = gl.runners.get(6) + runner.delete() + gl.runners.delete(6) + + +def test_disable_project_runner(gl: gitlab.Gitlab, resp_runner_disable): + gl.projects.get(1, lazy=True).runners.delete(6) + + +def test_disable_group_runner(gl: gitlab.Gitlab, resp_runner_disable): + gl.groups.get(1, lazy=True).runners.delete(6) + + +def test_enable_project_runner(gl: gitlab.Gitlab, resp_runner_enable): + runner = gl.projects.get(1, lazy=True).runners.create({"runner_id": 6}) + assert runner.active == True + assert runner.id == 6 + assert runner.name == "test-name" + + +def test_enable_group_runner(gl: gitlab.Gitlab, resp_runner_enable): + runner = gl.groups.get(1, lazy=True).runners.create({"runner_id": 6}) + assert runner.active == True + assert runner.id == 6 + assert runner.name == "test-name" + + +def test_verify_runner(gl: gitlab.Gitlab, resp_runner_verify): + gl.runners.verify("token") + + +def test_runner_jobs(gl: gitlab.Gitlab, resp_get_runners_jobs): + jobs = gl.runners.get(6, lazy=True).jobs.list() + assert jobs[0].duration == 120 + assert jobs[0].name == "test" + assert jobs[0].user.get("name") == "John Doe2" + assert len(jobs) == 1 diff --git a/test-requirements.txt b/test-requirements.txt index c78843606..ed5d6392f 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,3 +7,4 @@ pytest pytest-cov sphinx>=1.3 sphinx_rtd_theme +responses