Skip to content

Commit 38e9fde

Browse files
Merge pull request #1089 from python-gitlab/feat/group-runners
feat: add group runners api
2 parents 89007c9 + 127fa5a commit 38e9fde

File tree

6 files changed

+313
-2
lines changed

6 files changed

+313
-2
lines changed

README.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ Before submitting a pull request make sure that the tests still succeed with
128128
your change. Unit tests and functional tests run using the travis service and
129129
passing tests are mandatory to get merge requests accepted.
130130

131+
We're currently in a restructing phase for the unit tests. If you're changing existing
132+
tests, feel free to keep the current format. Otherwise please write new tests with pytest and
133+
using `responses<https://github.com/getsentry/responses>`_. An example for new tests can be found in
134+
tests/objects/test_runner.py
135+
131136
You need to install ``tox`` to run unit tests and documentation builds locally:
132137

133138
.. code-block:: bash

docs/gl_objects/runners.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Verify a registered runner token::
7878
except GitlabVerifyError:
7979
print("Invalid token")
8080

81-
Project runners
81+
Project/Group runners
8282
===============
8383

8484
Reference
@@ -89,6 +89,9 @@ Reference
8989
+ :class:`gitlab.v4.objects.ProjectRunner`
9090
+ :class:`gitlab.v4.objects.ProjectRunnerManager`
9191
+ :attr:`gitlab.v4.objects.Project.runners`
92+
+ :class:`gitlab.v4.objects.GroupRunner`
93+
+ :class:`gitlab.v4.objects.GroupRunnerManager`
94+
+ :attr:`gitlab.v4.objects.Group.runners`
9295

9396
* GitLab API: https://docs.gitlab.com/ce/api/runners.html
9497

gitlab/tests/conftest.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import pytest
2+
import gitlab
3+
4+
5+
@pytest.fixture
6+
def gl():
7+
return gitlab.Gitlab(
8+
"http://localhost",
9+
private_token="private_token",
10+
ssl_verify=True,
11+
api_version=4,
12+
)

gitlab/tests/objects/test_runners.py

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
import unittest
2+
import responses
3+
import gitlab
4+
import pytest
5+
import re
6+
from .mocks import * # noqa
7+
8+
9+
runner_detail = {
10+
"active": True,
11+
"architecture": "amd64",
12+
"description": "test-1-20150125",
13+
"id": 6,
14+
"ip_address": "127.0.0.1",
15+
"is_shared": False,
16+
"contacted_at": "2016-01-25T16:39:48.066Z",
17+
"name": "test-runner",
18+
"online": True,
19+
"status": "online",
20+
"platform": "linux",
21+
"projects": [
22+
{
23+
"id": 1,
24+
"name": "GitLab Community Edition",
25+
"name_with_namespace": "GitLab.org / GitLab Community Edition",
26+
"path": "gitlab-foss",
27+
"path_with_namespace": "gitlab-org/gitlab-foss",
28+
}
29+
],
30+
"revision": "5nj35",
31+
"tag_list": ["ruby", "mysql"],
32+
"version": "v13.0.0",
33+
"access_level": "ref_protected",
34+
"maximum_timeout": 3600,
35+
}
36+
37+
runner_shortinfo = {
38+
"active": True,
39+
"description": "test-1-20150125",
40+
"id": 6,
41+
"is_shared": False,
42+
"ip_address": "127.0.0.1",
43+
"name": "test-name",
44+
"online": True,
45+
"status": "online",
46+
}
47+
48+
runner_jobs = [
49+
{
50+
"id": 6,
51+
"ip_address": "127.0.0.1",
52+
"status": "running",
53+
"stage": "test",
54+
"name": "test",
55+
"ref": "master",
56+
"tag": False,
57+
"coverage": "99%",
58+
"created_at": "2017-11-16T08:50:29.000Z",
59+
"started_at": "2017-11-16T08:51:29.000Z",
60+
"finished_at": "2017-11-16T08:53:29.000Z",
61+
"duration": 120,
62+
"user": {
63+
"id": 1,
64+
"name": "John Doe2",
65+
"username": "user2",
66+
"state": "active",
67+
"avatar_url": "http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon",
68+
"web_url": "http://localhost/user2",
69+
"created_at": "2017-11-16T18:38:46.000Z",
70+
"bio": None,
71+
"location": None,
72+
"public_email": "",
73+
"skype": "",
74+
"linkedin": "",
75+
"twitter": "",
76+
"website_url": "",
77+
"organization": None,
78+
},
79+
}
80+
]
81+
82+
83+
@pytest.fixture
84+
def resp_get_runners_jobs():
85+
with responses.RequestsMock() as rsps:
86+
rsps.add(
87+
method=responses.GET,
88+
url="http://localhost/api/v4/runners/6/jobs",
89+
json=runner_jobs,
90+
content_type="application/json",
91+
status=200,
92+
)
93+
yield rsps
94+
95+
96+
@pytest.fixture
97+
def resp_get_runners_list():
98+
with responses.RequestsMock() as rsps:
99+
rsps.add(
100+
method=responses.GET,
101+
url=re.compile(r".*?(/runners(/all)?|/(groups|projects)/1/runners)"),
102+
json=[runner_shortinfo],
103+
content_type="application/json",
104+
status=200,
105+
)
106+
yield rsps
107+
108+
109+
@pytest.fixture
110+
def resp_runner_detail():
111+
with responses.RequestsMock() as rsps:
112+
pattern = re.compile(r".*?/runners/6")
113+
rsps.add(
114+
method=responses.GET,
115+
url=pattern,
116+
json=runner_detail,
117+
content_type="application/json",
118+
status=200,
119+
)
120+
rsps.add(
121+
method=responses.PUT,
122+
url=pattern,
123+
json=runner_detail,
124+
content_type="application/json",
125+
status=200,
126+
)
127+
yield rsps
128+
129+
130+
@pytest.fixture
131+
def resp_runner_register():
132+
with responses.RequestsMock() as rsps:
133+
pattern = re.compile(r".*?/runners")
134+
rsps.add(
135+
method=responses.POST,
136+
url=pattern,
137+
json={"id": "6", "token": "6337ff461c94fd3fa32ba3b1ff4125"},
138+
content_type="application/json",
139+
status=200,
140+
)
141+
yield rsps
142+
143+
144+
@pytest.fixture
145+
def resp_runner_enable():
146+
with responses.RequestsMock() as rsps:
147+
pattern = re.compile(r".*?(projects|groups)/1/runners")
148+
rsps.add(
149+
method=responses.POST,
150+
url=pattern,
151+
json=runner_shortinfo,
152+
content_type="application/json",
153+
status=200,
154+
)
155+
yield rsps
156+
157+
158+
@pytest.fixture
159+
def resp_runner_delete():
160+
with responses.RequestsMock() as rsps:
161+
pattern = re.compile(r".*?/runners/6")
162+
rsps.add(
163+
method=responses.GET,
164+
url=pattern,
165+
json=runner_detail,
166+
content_type="application/json",
167+
status=200,
168+
)
169+
rsps.add(
170+
method=responses.DELETE, url=pattern, status=204,
171+
)
172+
yield rsps
173+
174+
175+
@pytest.fixture
176+
def resp_runner_disable():
177+
with responses.RequestsMock() as rsps:
178+
pattern = re.compile(r".*?/(groups|projects)/1/runners/6")
179+
rsps.add(
180+
method=responses.DELETE, url=pattern, status=204,
181+
)
182+
yield rsps
183+
184+
185+
@pytest.fixture
186+
def resp_runner_verify():
187+
with responses.RequestsMock() as rsps:
188+
pattern = re.compile(r".*?/runners/verify")
189+
rsps.add(
190+
method=responses.POST, url=pattern, status=200,
191+
)
192+
yield rsps
193+
194+
195+
def test_owned_runners_list(gl: gitlab.Gitlab, resp_get_runners_list):
196+
runners = gl.runners.list()
197+
assert runners[0].active == True
198+
assert runners[0].id == 6
199+
assert runners[0].name == "test-name"
200+
assert len(runners) == 1
201+
202+
203+
def test_project_runners_list(gl: gitlab.Gitlab, resp_get_runners_list):
204+
runners = gl.projects.get(1, lazy=True).runners.list()
205+
assert runners[0].active == True
206+
assert runners[0].id == 6
207+
assert runners[0].name == "test-name"
208+
assert len(runners) == 1
209+
210+
211+
def test_group_runners_list(gl: gitlab.Gitlab, resp_get_runners_list):
212+
runners = gl.groups.get(1, lazy=True).runners.list()
213+
assert runners[0].active == True
214+
assert runners[0].id == 6
215+
assert runners[0].name == "test-name"
216+
assert len(runners) == 1
217+
218+
219+
def test_all_runners_list(gl: gitlab.Gitlab, resp_get_runners_list):
220+
runners = gl.runners.all()
221+
assert runners[0].active == True
222+
assert runners[0].id == 6
223+
assert runners[0].name == "test-name"
224+
assert len(runners) == 1
225+
226+
227+
def test_create_runner(gl: gitlab.Gitlab, resp_runner_register):
228+
runner = gl.runners.create({"token": "token"})
229+
assert runner.id == "6"
230+
assert runner.token == "6337ff461c94fd3fa32ba3b1ff4125"
231+
232+
233+
def test_get_update_runner(gl: gitlab.Gitlab, resp_runner_detail):
234+
runner = gl.runners.get(6)
235+
assert runner.active == True
236+
runner.tag_list.append("new")
237+
runner.save()
238+
239+
240+
def test_remove_runner(gl: gitlab.Gitlab, resp_runner_delete):
241+
runner = gl.runners.get(6)
242+
runner.delete()
243+
gl.runners.delete(6)
244+
245+
246+
def test_disable_project_runner(gl: gitlab.Gitlab, resp_runner_disable):
247+
gl.projects.get(1, lazy=True).runners.delete(6)
248+
249+
250+
def test_disable_group_runner(gl: gitlab.Gitlab, resp_runner_disable):
251+
gl.groups.get(1, lazy=True).runners.delete(6)
252+
253+
254+
def test_enable_project_runner(gl: gitlab.Gitlab, resp_runner_enable):
255+
runner = gl.projects.get(1, lazy=True).runners.create({"runner_id": 6})
256+
assert runner.active == True
257+
assert runner.id == 6
258+
assert runner.name == "test-name"
259+
260+
261+
def test_enable_group_runner(gl: gitlab.Gitlab, resp_runner_enable):
262+
runner = gl.groups.get(1, lazy=True).runners.create({"runner_id": 6})
263+
assert runner.active == True
264+
assert runner.id == 6
265+
assert runner.name == "test-name"
266+
267+
268+
def test_verify_runner(gl: gitlab.Gitlab, resp_runner_verify):
269+
gl.runners.verify("token")
270+
271+
272+
def test_runner_jobs(gl: gitlab.Gitlab, resp_get_runners_jobs):
273+
jobs = gl.runners.get(6, lazy=True).jobs.list()
274+
assert jobs[0].duration == 120
275+
assert jobs[0].name == "test"
276+
assert jobs[0].user.get("name") == "John Doe2"
277+
assert len(jobs) == 1

gitlab/v4/objects.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,17 @@ class GroupProjectManager(ListMixin, RESTManager):
13081308
)
13091309

13101310

1311+
class GroupRunner(ObjectDeleteMixin, RESTObject):
1312+
pass
1313+
1314+
1315+
class GroupRunnerManager(NoUpdateMixin, RESTManager):
1316+
_path = "/groups/%(group_id)s/runners"
1317+
_obj_cls = GroupRunner
1318+
_from_parent_attrs = {"group_id": "id"}
1319+
_create_attrs = (("runner_id",), tuple())
1320+
1321+
13111322
class GroupSubgroup(RESTObject):
13121323
pass
13131324

@@ -1357,6 +1368,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
13571368
("milestones", "GroupMilestoneManager"),
13581369
("notificationsettings", "GroupNotificationSettingsManager"),
13591370
("projects", "GroupProjectManager"),
1371+
("runners", "GroupRunnerManager"),
13601372
("subgroups", "GroupSubgroupManager"),
13611373
("variables", "GroupVariableManager"),
13621374
("clusters", "GroupClusterManager"),
@@ -5382,7 +5394,8 @@ def all(self, scope=None, **kwargs):
53825394
query_data = {}
53835395
if scope is not None:
53845396
query_data["scope"] = scope
5385-
return self.gitlab.http_list(path, query_data, **kwargs)
5397+
obj = self.gitlab.http_list(path, query_data, **kwargs)
5398+
return [self._obj_cls(self, item) for item in obj]
53865399

53875400
@cli.register_custom_action("RunnerManager", ("token",))
53885401
@exc.on_http_error(exc.GitlabVerifyError)

test-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ pytest
77
pytest-cov
88
sphinx>=1.3
99
sphinx_rtd_theme
10+
responses

0 commit comments

Comments
 (0)