Skip to content

Commit bf2fa02

Browse files
feat(objects): add Project CI Lint support
Add support for validating a project's CI configuration [1] [1] https://docs.gitlab.com/ee/api/lint.html
1 parent 3df404c commit bf2fa02

File tree

3 files changed

+111
-0
lines changed

3 files changed

+111
-0
lines changed

docs/gl_objects/projects.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,49 @@ Get total fetches in last 30 days of a project::
819819

820820
total_fetches = project.additionalstatistics.get().fetches['total']
821821

822+
Project CI Lint
823+
=============================
824+
825+
Reference
826+
---------
827+
828+
* v4 API:
829+
830+
+ :class:`gitlab.v4.objects.ProjectCiLint`
831+
+ :class:`gitlab.v4.objects.ProjectCiLintManager`
832+
+ :attr:`gitlab.v4.objects.Project.ci_lint`
833+
834+
* GitLab API: https://docs.gitlab.com/ee/api/lint.html
835+
836+
Examples
837+
---------
838+
839+
Validate a project's CI configuration::
840+
841+
lint_result = project.ci_lint.get()
842+
assert lint_result.valid is True # Test that the .gitlab-ci.yml is valid
843+
print(lint_result.merged_yaml) # Print the merged YAML file
844+
845+
Validate a CI YAML configuration with a namespace:
846+
847+
gitlab_ci_yml = """.api_test:
848+
rules:
849+
- if: $CI_PIPELINE_SOURCE=="merge_request_event"
850+
changes:
851+
- src/api/*
852+
deploy:
853+
extends:
854+
- .api_test
855+
rules:
856+
- when: manual
857+
allow_failure: true
858+
script:
859+
- echo "hello world"
860+
"""
861+
lint_result = project.ci_lint.create({"content": gitlab_ci_yml})
862+
assert lint_result.valid is True # Test that the .gitlab-ci.yml is valid
863+
print(lint_result.merged_yaml) # Print the merged YAML file
864+
822865
Project storage
823866
=============================
824867

gitlab/v4/objects/projects.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
"""
2+
GitLab API:
3+
https://docs.gitlab.com/ee/api/projects.html
4+
https://docs.gitlab.com/ee/api/lint.html#validate-a-projects-ci-configuration
5+
"""
16
from typing import (
27
Any,
38
Callable,
@@ -97,6 +102,8 @@
97102
"ProjectRemoteMirrorManager",
98103
"ProjectStorage",
99104
"ProjectStorageManager",
105+
"ProjectCiLint",
106+
"ProjectCiLintManager",
100107
]
101108

102109

@@ -158,6 +165,7 @@ class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTO
158165
badges: ProjectBadgeManager
159166
boards: ProjectBoardManager
160167
branches: ProjectBranchManager
168+
ci_lint: "ProjectCiLintManager"
161169
clusters: ProjectClusterManager
162170
commits: ProjectCommitManager
163171
customattributes: ProjectCustomAttributeManager
@@ -1055,3 +1063,17 @@ class ProjectStorageManager(GetWithoutIdMixin, RESTManager):
10551063

10561064
def get(self, **kwargs: Any) -> ProjectStorage:
10571065
return cast(ProjectStorage, super().get(**kwargs))
1066+
1067+
1068+
class ProjectCiLint(RESTObject):
1069+
pass
1070+
1071+
1072+
class ProjectCiLintManager(GetWithoutIdMixin, CreateMixin, RESTManager):
1073+
_path = "/projects/{project_id}/ci/lint"
1074+
_obj_cls = ProjectCiLint
1075+
_from_parent_attrs = {"project_id": "id"}
1076+
# https://docs.gitlab.com/ee/api/lint.html#validate-a-projects-ci-configuration
1077+
1078+
def get(self, **kwargs: Any) -> ProjectCiLint:
1079+
return cast(ProjectCiLint, super().get(**kwargs))

tests/unit/objects/test_projects.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@
8282
"status": "created",
8383
"source": "trigger",
8484
}
85+
ci_lint_get_content = {
86+
"valid": True,
87+
"merged_yaml": "---\n:test_job:\n :script: echo 1\n",
88+
"errors": [],
89+
"warnings": [],
90+
}
8591

8692

8793
@pytest.fixture
@@ -541,6 +547,32 @@ def resp_artifact():
541547
yield rsps
542548

543549

550+
@pytest.fixture
551+
def resp_get_ci_lint():
552+
with responses.RequestsMock() as rsps:
553+
rsps.add(
554+
method=responses.GET,
555+
url="http://localhost/api/v4/projects/1/ci/lint",
556+
json=ci_lint_get_content,
557+
content_type="application/json",
558+
status=200,
559+
)
560+
yield rsps
561+
562+
563+
@pytest.fixture
564+
def resp_create_ci_lint():
565+
with responses.RequestsMock() as rsps:
566+
rsps.add(
567+
method=responses.POST,
568+
url="http://localhost/api/v4/projects/1/ci/lint",
569+
json=ci_lint_get_content,
570+
content_type="application/json",
571+
status=200,
572+
)
573+
yield rsps
574+
575+
544576
def test_get_project(gl, resp_get_project):
545577
data = gl.projects.get(1)
546578
assert isinstance(data, Project)
@@ -756,3 +788,17 @@ def test_project_pull_mirror(project, resp_start_pull_mirroring_project):
756788
def test_project_snapshot(project, resp_snapshot_project):
757789
tar_file = project.snapshot()
758790
assert isinstance(tar_file, bytes)
791+
792+
793+
def test_project_ci_lint_get(project, resp_get_ci_lint):
794+
lint_result = project.ci_lint.get()
795+
assert lint_result.valid is True
796+
797+
798+
def test_project_ci_lint_create(project, resp_create_ci_lint):
799+
gitlab_ci_yml = """---
800+
:test_job:
801+
:script: echo 1
802+
"""
803+
lint_result = project.ci_lint.create({"content": gitlab_ci_yml})
804+
assert lint_result.valid is True

0 commit comments

Comments
 (0)