diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index b5a5d66ab..4c67e41c6 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -819,6 +819,49 @@ Get total fetches in last 30 days of a project:: total_fetches = project.additionalstatistics.get().fetches['total'] +Project CI Lint +============================= + +Reference +--------- + +* v4 API: + + + :class:`gitlab.v4.objects.ProjectCiLint` + + :class:`gitlab.v4.objects.ProjectCiLintManager` + + :attr:`gitlab.v4.objects.Project.ci_lint` + +* GitLab API: https://docs.gitlab.com/ee/api/lint.html + +Examples +--------- + +Validate a project's CI configuration:: + + lint_result = project.ci_lint.get() + assert lint_result.valid is True # Test that the .gitlab-ci.yml is valid + print(lint_result.merged_yaml) # Print the merged YAML file + +Validate a CI YAML configuration with a namespace:: + + gitlab_ci_yml = """.api_test: + rules: + - if: $CI_PIPELINE_SOURCE=="merge_request_event" + changes: + - src/api/* + deploy: + extends: + - .api_test + rules: + - when: manual + allow_failure: true + script: + - echo "hello world" + """ + lint_result = project.ci_lint.create({"content": gitlab_ci_yml}) + assert lint_result.valid is True # Test that the .gitlab-ci.yml is valid + print(lint_result.merged_yaml) # Print the merged YAML file + Project storage ============================= diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py index e912a280f..367ab6892 100644 --- a/gitlab/v4/objects/projects.py +++ b/gitlab/v4/objects/projects.py @@ -1,3 +1,8 @@ +""" +GitLab API: +https://docs.gitlab.com/ee/api/projects.html +https://docs.gitlab.com/ee/api/lint.html +""" from typing import ( Any, Callable, @@ -97,6 +102,8 @@ "ProjectRemoteMirrorManager", "ProjectStorage", "ProjectStorageManager", + "ProjectCiLint", + "ProjectCiLintManager", ] @@ -158,6 +165,7 @@ class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTO badges: ProjectBadgeManager boards: ProjectBoardManager branches: ProjectBranchManager + ci_lint: "ProjectCiLintManager" clusters: ProjectClusterManager commits: ProjectCommitManager customattributes: ProjectCustomAttributeManager @@ -1055,3 +1063,18 @@ class ProjectStorageManager(GetWithoutIdMixin, RESTManager): def get(self, **kwargs: Any) -> ProjectStorage: return cast(ProjectStorage, super().get(**kwargs)) + + +class ProjectCiLint(RESTObject): + pass + + +class ProjectCiLintManager(GetWithoutIdMixin, CreateMixin, RESTManager): + """GitLab API: https://docs.gitlab.com/ee/api/lint.html""" + + _path = "/projects/{project_id}/ci/lint" + _obj_cls = ProjectCiLint + _from_parent_attrs = {"project_id": "id"} + + def get(self, **kwargs: Any) -> ProjectCiLint: + return cast(ProjectCiLint, super().get(**kwargs)) diff --git a/tests/unit/objects/test_projects.py b/tests/unit/objects/test_projects.py index 85bae8600..4db88356a 100644 --- a/tests/unit/objects/test_projects.py +++ b/tests/unit/objects/test_projects.py @@ -82,6 +82,12 @@ "status": "created", "source": "trigger", } +ci_lint_get_content = { + "valid": True, + "merged_yaml": "---\n:test_job:\n :script: echo 1\n", + "errors": [], + "warnings": [], +} @pytest.fixture @@ -541,6 +547,32 @@ def resp_artifact(): yield rsps +@pytest.fixture +def resp_get_ci_lint(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.GET, + url="http://localhost/api/v4/projects/1/ci/lint", + json=ci_lint_get_content, + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_create_ci_lint(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.POST, + url="http://localhost/api/v4/projects/1/ci/lint", + json=ci_lint_get_content, + content_type="application/json", + status=200, + ) + yield rsps + + def test_get_project(gl, resp_get_project): data = gl.projects.get(1) assert isinstance(data, Project) @@ -756,3 +788,17 @@ def test_project_pull_mirror(project, resp_start_pull_mirroring_project): def test_project_snapshot(project, resp_snapshot_project): tar_file = project.snapshot() assert isinstance(tar_file, bytes) + + +def test_project_ci_lint_get(project, resp_get_ci_lint): + lint_result = project.ci_lint.get() + assert lint_result.valid is True + + +def test_project_ci_lint_create(project, resp_create_ci_lint): + gitlab_ci_yml = """--- +:test_job: + :script: echo 1 +""" + lint_result = project.ci_lint.create({"content": gitlab_ci_yml}) + assert lint_result.valid is True