Skip to content

feat(api): add support for external status check #3098

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api-objects.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ API examples
gl_objects/settings
gl_objects/snippets
gl_objects/statistics
gl_objects/status_checks
gl_objects/system_hooks
gl_objects/templates
gl_objects/todos
Expand Down
57 changes: 57 additions & 0 deletions docs/gl_objects/status_checks.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#######################
External Status Checks
#######################

Manage external status checks for projects and merge requests.


Project external status checks
===============================

Reference
---------

* v4 API:

+ :class:`gitlab.v4.objects.ProjectExternalStatusCheck`
+ :class:`gitlab.v4.objects.ProjectExternalStatusCheckManager`
+ :attr:`gitlab.v4.objects.Project.external_status_checks`

* GitLab API: https://docs.gitlab.com/ee/api/status_checks.html

Examples
---------

List external status checks for a project::

status_checks = project.external_status_checks.list()

Create an external status check with shared secret::

status_checks = project.external_status_checks.create({
"name": "mr_blocker",
"external_url": "https://example.com/mr-status-check",
"shared_secret": "secret-string"
})

Create an external status check with shared secret for protected branches::

protected_branch = project.protectedbranches.get('main')

status_check = project.external_status_checks.create({
"name": "mr_blocker",
"external_url": "https://example.com/mr-status-check",
"shared_secret": "secret-string",
"protected_branch_ids": [protected_branch.id]
})


Update an external status check::

status_check.external_url = "https://example.com/mr-blocker"
status_check.save()

Delete an external status check::

status_check.delete(status_check_id)

1 change: 1 addition & 0 deletions gitlab/v4/objects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
from .sidekiq import *
from .snippets import *
from .statistics import *
from .status_checks import *
from .tags import *
from .templates import *
from .todos import *
Expand Down
2 changes: 2 additions & 0 deletions gitlab/v4/objects/merge_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from .notes import ProjectMergeRequestNoteManager # noqa: F401
from .pipelines import ProjectMergeRequestPipelineManager # noqa: F401
from .reviewers import ProjectMergeRequestReviewerDetailManager
from .status_checks import ProjectMergeRequestStatusCheckManager

__all__ = [
"MergeRequest",
Expand Down Expand Up @@ -167,6 +168,7 @@ class ProjectMergeRequest(
resourcemilestoneevents: ProjectMergeRequestResourceMilestoneEventManager
resourcestateevents: ProjectMergeRequestResourceStateEventManager
reviewer_details: ProjectMergeRequestReviewerDetailManager
status_checks: ProjectMergeRequestStatusCheckManager

@cli.register_custom_action(cls_names="ProjectMergeRequest")
@exc.on_http_error(exc.GitlabMROnBuildSuccessError)
Expand Down
2 changes: 2 additions & 0 deletions gitlab/v4/objects/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
ProjectAdditionalStatisticsManager,
ProjectIssuesStatisticsManager,
)
from .status_checks import ProjectExternalStatusCheckManager # noqa: F401
from .tags import ProjectProtectedTagManager, ProjectTagManager # noqa: F401
from .templates import ( # noqa: F401
ProjectDockerfileTemplateManager,
Expand Down Expand Up @@ -253,6 +254,7 @@ class Project(
secure_files: ProjectSecureFileManager
services: ProjectServiceManager
snippets: ProjectSnippetManager
external_status_checks: ProjectExternalStatusCheckManager
storage: "ProjectStorageManager"
tags: ProjectTagManager
triggers: ProjectTriggerManager
Expand Down
52 changes: 52 additions & 0 deletions gitlab/v4/objects/status_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from gitlab.base import RESTManager, RESTObject
from gitlab.mixins import (
CreateMixin,
DeleteMixin,
ListMixin,
ObjectDeleteMixin,
SaveMixin,
UpdateMethod,
UpdateMixin,
)
from gitlab.types import ArrayAttribute, RequiredOptional

__all__ = [
"ProjectExternalStatusCheck",
"ProjectExternalStatusCheckManager",
"ProjectMergeRequestStatusCheck",
"ProjectMergeRequestStatusCheckManager",
]


class ProjectExternalStatusCheck(SaveMixin, ObjectDeleteMixin, RESTObject):
pass


class ProjectExternalStatusCheckManager(
ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager
):
_path = "/projects/{project_id}/external_status_checks"
_obj_cls = ProjectExternalStatusCheck
_from_parent_attrs = {"project_id": "id"}
_create_attrs = RequiredOptional(
required=("name", "external_url"),
optional=("shared_secret", "protected_branch_ids"),
)
_update_attrs = RequiredOptional(
optional=("name", "external_url", "shared_secret", "protected_branch_ids")
)
_types = {"protected_branch_ids": ArrayAttribute}


class ProjectMergeRequestStatusCheck(SaveMixin, RESTObject):
pass


class ProjectMergeRequestStatusCheckManager(ListMixin, RESTManager):
_path = "/projects/{project_id}/merge_requests/{merge_request_iid}/status_checks"
_obj_cls = ProjectMergeRequestStatusCheck
_from_parent_attrs = {"project_id": "project_id", "merge_request_iid": "iid"}
_update_attrs = RequiredOptional(
required=("sha", "external_status_check_id", "status")
)
_update_method = UpdateMethod.POST
16 changes: 16 additions & 0 deletions tests/functional/api/test_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,19 @@ def test_project_transfer(gl, project, group):

project = gl.projects.get(project.id)
assert project.namespace["path"] == gl.user.username


@pytest.mark.gitlab_premium
def test_project_external_status_check_create(gl, project):
status_check = project.external_status_checks.create(
{"name": "MR blocker", "external_url": "https://example.com/mr-blocker"}
)
assert status_check.name == "MR blocker"
assert status_check.external_url == "https://example.com/mr-blocker"


@pytest.mark.gitlab_premium
def test_project_external_status_check_list(gl, project):
status_checks = project.external_status_checks.list()

assert len(status_checks) == 1
127 changes: 127 additions & 0 deletions tests/unit/objects/test_status_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
"""
GitLab API: https://docs.gitlab.com/ee/api/status_checks.html
"""

import pytest
import responses


@pytest.fixture
def external_status_check():
return {
"id": 1,
"name": "MR blocker",
"project_id": 1,
"external_url": "https://example.com/mr-blocker",
"hmac": True,
"protected_branches": [
{
"id": 1,
"project_id": 1,
"name": "main",
"created_at": "2020-10-12T14:04:50.787Z",
"updated_at": "2020-10-12T14:04:50.787Z",
"code_owner_approval_required": False,
}
],
}


@pytest.fixture
def updated_external_status_check():
return {
"id": 1,
"name": "Updated MR blocker",
"project_id": 1,
"external_url": "https://example.com/mr-blocker",
"hmac": True,
"protected_branches": [
{
"id": 1,
"project_id": 1,
"name": "main",
"created_at": "2020-10-12T14:04:50.787Z",
"updated_at": "2020-10-12T14:04:50.787Z",
"code_owner_approval_required": False,
}
],
}


@pytest.fixture
def resp_list_external_status_checks(external_status_check):
with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
rsps.add(
method=responses.GET,
url="http://localhost/api/v4/projects/1/external_status_checks",
json=[external_status_check],
content_type="application/json",
status=200,
)
yield rsps


@pytest.fixture
def resp_create_external_status_checks(external_status_check):
with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
rsps.add(
method=responses.POST,
url="http://localhost/api/v4/projects/1/external_status_checks",
json=external_status_check,
content_type="application/json",
status=200,
)
yield rsps


@pytest.fixture
def resp_update_external_status_checks(updated_external_status_check):
with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
rsps.add(
method=responses.PUT,
url="http://localhost/api/v4/groups/1/external_status_checks",
json=updated_external_status_check,
content_type="application/json",
status=200,
)
yield rsps


@pytest.fixture
def resp_delete_external_status_checks():
content = []

with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
rsps.add(
method=responses.DELETE,
url="http://localhost/api/v4/projects/1/external_status_checks/1",
status=204,
)
rsps.add(
method=responses.GET,
url="http://localhost/api/v4/projects/1/external_status_checks",
json=content,
content_type="application/json",
status=200,
)
yield rsps


def test_list_external_status_checks(gl, resp_list_external_status_checks):
status_checks = gl.projects.get(1, lazy=True).external_status_checks.list()
assert len(status_checks) == 1
assert status_checks[0].name == "MR blocker"


def test_create_external_status_checks(gl, resp_create_external_status_checks):
access_token = gl.projects.get(1, lazy=True).external_status_checks.create(
{"name": "MR blocker", "external_url": "https://example.com/mr-blocker"}
)
assert access_token.name == "MR blocker"
assert access_token.external_url == "https://example.com/mr-blocker"


def test_delete_external_status_checks(gl, resp_delete_external_status_checks):
gl.projects.get(1, lazy=True).external_status_checks.delete(1)
status_checks = gl.projects.get(1, lazy=True).external_status_checks.list()
assert len(status_checks) == 0
Loading