Skip to content

Commit 98f27fe

Browse files
feat(api): add support for external status check
1 parent e3cb806 commit 98f27fe

File tree

9 files changed

+261
-0
lines changed

9 files changed

+261
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ venv/
2424
!.pre-commit-config.yaml
2525
!.readthedocs.yml
2626
!.renovaterc.json
27+
28+
# Exclude license file/s
29+
python-gitlab-ci.gitlab-license

docs/api-objects.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ API examples
6363
gl_objects/settings
6464
gl_objects/snippets
6565
gl_objects/statistics
66+
gl_objects/status_checks
6667
gl_objects/system_hooks
6768
gl_objects/templates
6869
gl_objects/todos

docs/gl_objects/status_checks.rst

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
############
2+
Status Checks
3+
############
4+
5+
Manage status checks for projects and merge requests.
6+
7+
8+
Project status checks
9+
========
10+
11+
Reference
12+
---------
13+
14+
* v4 API:
15+
16+
+ :class:`gitlab.v4.objects.ProjectStatusCheck`
17+
+ :class:`gitlab.v4.objects.ProjectStatusCheckManager`
18+
+ :attr:`gitlab.v4.objects.Project.status_checks`
19+
20+
* GitLab API: https://docs.gitlab.com/ee/api/status_checks.html
21+
22+
Examples
23+
---------
24+
25+
List status checks for a project::
26+
27+
status_checks = project.status_checks.list()
28+
29+
Create a status check with shared secret::
30+
31+
status_checks = project.status_checks.create({
32+
"name": "mr_blocker",
33+
"external_url": "https://example.com/mr-status-check",
34+
"shared_secret": "secret-string"
35+
})
36+
37+
Create a status check with shared secret for protected branches::
38+
39+
protected_branch = project.protectedbranches.get('main')
40+
41+
status_check = project.status_checks.create({
42+
"name": "mr_blocker",
43+
"external_url": "https://example.com/mr-status-check",
44+
"shared_secret": "secret-string",
45+
"protected_branch_ids": [protected_branch.id]
46+
})
47+
48+
49+
Update a status check::
50+
51+
status_check.external_url = "https://example.com/mr-blocker"
52+
status_check.save()
53+
54+
Delete a status check::
55+
56+
status_check.delete(status_check_id)
57+

gitlab/v4/objects/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
from .sidekiq import *
6969
from .snippets import *
7070
from .statistics import *
71+
from .status_checks import *
7172
from .tags import *
7273
from .templates import *
7374
from .todos import *

gitlab/v4/objects/merge_requests.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from .notes import ProjectMergeRequestNoteManager # noqa: F401
4545
from .pipelines import ProjectMergeRequestPipelineManager # noqa: F401
4646
from .reviewers import ProjectMergeRequestReviewerDetailManager
47+
from .status_checks import ProjectMergeRequestStatusCheckManager
4748

4849
__all__ = [
4950
"MergeRequest",
@@ -167,6 +168,7 @@ class ProjectMergeRequest(
167168
resourcemilestoneevents: ProjectMergeRequestResourceMilestoneEventManager
168169
resourcestateevents: ProjectMergeRequestResourceStateEventManager
169170
reviewer_details: ProjectMergeRequestReviewerDetailManager
171+
status_checks: ProjectMergeRequestStatusCheckManager
170172

171173
@cli.register_custom_action(cls_names="ProjectMergeRequest")
172174
@exc.on_http_error(exc.GitlabMROnBuildSuccessError)

gitlab/v4/objects/projects.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
ProjectAdditionalStatisticsManager,
105105
ProjectIssuesStatisticsManager,
106106
)
107+
from .status_checks import ProjectStatusCheckManager # noqa: F401
107108
from .tags import ProjectProtectedTagManager, ProjectTagManager # noqa: F401
108109
from .templates import ( # noqa: F401
109110
ProjectDockerfileTemplateManager,
@@ -253,6 +254,7 @@ class Project(
253254
secure_files: ProjectSecureFileManager
254255
services: ProjectServiceManager
255256
snippets: ProjectSnippetManager
257+
status_checks: ProjectStatusCheckManager
256258
storage: "ProjectStorageManager"
257259
tags: ProjectTagManager
258260
triggers: ProjectTriggerManager

gitlab/v4/objects/status_checks.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from gitlab.base import RESTManager, RESTObject
2+
from gitlab.mixins import (
3+
CreateMixin,
4+
DeleteMixin,
5+
ListMixin,
6+
ObjectDeleteMixin,
7+
SaveMixin,
8+
UpdateMethod,
9+
UpdateMixin,
10+
)
11+
from gitlab.types import ArrayAttribute, RequiredOptional
12+
13+
__all__ = [
14+
"ProjectStatusCheck",
15+
"ProjectStatusCheckManager",
16+
"ProjectMergeRequestStatusCheck",
17+
"ProjectMergeRequestStatusCheckManager",
18+
]
19+
20+
21+
class ProjectStatusCheck(SaveMixin, ObjectDeleteMixin, RESTObject):
22+
pass
23+
24+
25+
class ProjectStatusCheckManager(
26+
ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
27+
):
28+
_path = "/projects/{project_id}/external_status_checks"
29+
_obj_cls = ProjectStatusCheck
30+
_from_parent_attrs = {"project_id": "id"}
31+
_create_attrs = RequiredOptional(
32+
required=("name", "external_url"),
33+
optional=("shared_secret", "protected_branch_ids"),
34+
)
35+
_update_attrs = RequiredOptional(
36+
optional=("name", "external_url", "shared_secret", "protected_branch_ids")
37+
)
38+
_types = {"protected_branch_ids": ArrayAttribute}
39+
40+
41+
class ProjectMergeRequestStatusCheck(SaveMixin, RESTObject):
42+
pass
43+
44+
45+
class ProjectMergeRequestStatusCheckManager(ListMixin, RESTManager):
46+
_path = "/projects/{project_id}/merge_requests/{merge_request_iid}/status_checks"
47+
_obj_cls = ProjectMergeRequestStatusCheck
48+
_from_parent_attrs = {"project_id": "id"}
49+
_update_attrs = RequiredOptional(
50+
required=("sha", "external_status_check_id", "status")
51+
)
52+
_update_method = UpdateMethod.POST

tests/functional/api/test_projects.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,3 +399,19 @@ def test_project_transfer(gl, project, group):
399399

400400
project = gl.projects.get(project.id)
401401
assert project.namespace["path"] == gl.user.username
402+
403+
404+
@pytest.mark.gitlab_premium
405+
def test_project_status_check_create(gl, project):
406+
status_check = project.status_checks.create(
407+
{"name": "MR blocker", "external_url": "https://example.com/mr-blocker"}
408+
)
409+
assert status_check.name == "MR blocker"
410+
assert status_check.external_url == "https://example.com/mr-blocker"
411+
412+
413+
@pytest.mark.gitlab_premium
414+
def test_project_status_check_list(gl, project):
415+
status_checks = project.status_checks.list()
416+
417+
assert len(status_checks) == 1
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""
2+
GitLab API: https://docs.gitlab.com/ee/api/status_checks.html
3+
"""
4+
5+
import pytest
6+
import responses
7+
8+
9+
@pytest.fixture
10+
def status_check():
11+
return {
12+
"id": 1,
13+
"name": "MR blocker",
14+
"project_id": 1,
15+
"external_url": "https://example.com/mr-blocker",
16+
"hmac": True,
17+
"protected_branches": [
18+
{
19+
"id": 1,
20+
"project_id": 1,
21+
"name": "main",
22+
"created_at": "2020-10-12T14:04:50.787Z",
23+
"updated_at": "2020-10-12T14:04:50.787Z",
24+
"code_owner_approval_required": False,
25+
}
26+
],
27+
}
28+
29+
30+
@pytest.fixture
31+
def updated_status_check():
32+
return {
33+
"id": 1,
34+
"name": "Updated MR blocker",
35+
"project_id": 1,
36+
"external_url": "https://example.com/mr-blocker",
37+
"hmac": True,
38+
"protected_branches": [
39+
{
40+
"id": 1,
41+
"project_id": 1,
42+
"name": "main",
43+
"created_at": "2020-10-12T14:04:50.787Z",
44+
"updated_at": "2020-10-12T14:04:50.787Z",
45+
"code_owner_approval_required": False,
46+
}
47+
],
48+
}
49+
50+
51+
@pytest.fixture
52+
def resp_list_status_checks(status_check):
53+
with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
54+
rsps.add(
55+
method=responses.GET,
56+
url="http://localhost/api/v4/projects/1/external_status_checks",
57+
json=[status_check],
58+
content_type="application/json",
59+
status=200,
60+
)
61+
yield rsps
62+
63+
64+
@pytest.fixture
65+
def resp_create_status_checks(status_check):
66+
with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
67+
rsps.add(
68+
method=responses.POST,
69+
url="http://localhost/api/v4/projects/1/external_status_checks",
70+
json=status_check,
71+
content_type="application/json",
72+
status=200,
73+
)
74+
yield rsps
75+
76+
77+
@pytest.fixture
78+
def resp_update_status_checks(updated_status_check):
79+
with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
80+
rsps.add(
81+
method=responses.PUT,
82+
url="http://localhost/api/v4/groups/1/external_status_checks",
83+
json=updated_status_check,
84+
content_type="application/json",
85+
status=200,
86+
)
87+
yield rsps
88+
89+
90+
@pytest.fixture
91+
def resp_delete_status_checks():
92+
content = []
93+
94+
with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
95+
rsps.add(
96+
method=responses.DELETE,
97+
url="http://localhost/api/v4/projects/1/external_status_checks/1",
98+
status=204,
99+
)
100+
rsps.add(
101+
method=responses.GET,
102+
url="http://localhost/api/v4/projects/1/external_status_checks",
103+
json=content,
104+
content_type="application/json",
105+
status=200,
106+
)
107+
yield rsps
108+
109+
110+
def test_list_status_checks(gl, resp_list_status_checks):
111+
status_checks = gl.projects.get(1, lazy=True).status_checks.list()
112+
assert len(status_checks) == 1
113+
assert status_checks[0].name == "MR blocker"
114+
115+
116+
def test_create_status_checks(gl, resp_create_status_checks):
117+
access_token = gl.projects.get(1, lazy=True).status_checks.create(
118+
{"name": "MR blocker", "external_url": "https://example.com/mr-blocker"}
119+
)
120+
assert access_token.name == "MR blocker"
121+
assert access_token.external_url == "https://example.com/mr-blocker"
122+
123+
124+
def test_delete_status_checks(gl, resp_delete_status_checks):
125+
gl.projects.get(1, lazy=True).status_checks.delete(1)
126+
status_checks = gl.projects.get(1, lazy=True).status_checks.list()
127+
assert len(status_checks) == 0

0 commit comments

Comments
 (0)