diff --git a/docs/api-objects.rst b/docs/api-objects.rst index e313bd8e8..a4e852be9 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -13,6 +13,7 @@ API examples gl_objects/branches gl_objects/clusters gl_objects/messages + gl_objects/ci_lint gl_objects/commits gl_objects/deploy_keys gl_objects/deploy_tokens diff --git a/docs/cli-examples.rst b/docs/cli-examples.rst index 5f4e0bca9..7f21f0308 100644 --- a/docs/cli-examples.rst +++ b/docs/cli-examples.rst @@ -6,6 +6,48 @@ CLI examples For a complete list of objects and actions available, see :doc:`/cli-objects`. +CI Lint +------- + +Lint a CI YAML configuration from a string: + +.. note:: + + To see output, you will need to use the ``-v``/``--verbose`` flag. + +.. code-block:: console + + $ gitlab --verbose ci-lint create --content \ + "--- + test: + script: + - echo hello + " + +Lint a CI YAML configuration from a file (see :ref:`cli_from_files`): + +.. code-block:: console + + $ gitlab --verbose ci-lint create --content @.gitlab-ci.yml + +Lint a project's CI YAML configuration: + +.. code-block:: console + + $ gitlab --verbose project-ci-lint create --project-id group/my-project --content @.gitlab-ci.yml + +Lint a project's current CI YAML configuration: + +.. code-block:: console + + $ gitlab --verbose project-ci-lint get --project-id group/my-project + +Lint a project's current CI YAML configuration on a specific branch: + +.. code-block:: console + + $ gitlab --verbose project-ci-lint get --project-id group/my-project --ref my-branch + Projects -------- diff --git a/docs/cli-usage.rst b/docs/cli-usage.rst index 6546f6674..5091ccba1 100644 --- a/docs/cli-usage.rst +++ b/docs/cli-usage.rst @@ -288,6 +288,8 @@ Example: $ gitlab -o yaml -f id,permissions -g elsewhere -c /tmp/gl.cfg project list +.. _cli_from_files: + Reading values from files ------------------------- diff --git a/docs/gl_objects/ci_lint.rst b/docs/gl_objects/ci_lint.rst new file mode 100644 index 000000000..6533db310 --- /dev/null +++ b/docs/gl_objects/ci_lint.rst @@ -0,0 +1,53 @@ +####### +CI Lint +####### + +Reference +--------- + +* v4 API: + + + :class:`gitlab.v4.objects.CiLint` + + :class:`gitlab.v4.objects.CiLintManager` + + :attr:`gitlab.Gitlab.ci_lint` + + :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 CI YAML configuration:: + + 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 = gl.ci_lint.create({"content": gitlab_ci_yml}) + + print(lint_result.status) # Print the status of the CI YAML + print(lint_result.merged_yaml) # Print the merged YAML file + +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:: + + 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 diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index a6ed0e638..82ec77f52 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -819,49 +819,6 @@ 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/client.py b/gitlab/client.py index 82eebbc7d..54409c3cd 100644 --- a/gitlab/client.py +++ b/gitlab/client.py @@ -126,6 +126,8 @@ def __init__( self.broadcastmessages = objects.BroadcastMessageManager(self) """See :class:`~gitlab.v4.objects.BroadcastMessageManager`""" + self.ci_lint = objects.CiLintManager(self) + """See :class:`~gitlab.v4.objects.CiLintManager`""" self.deploykeys = objects.DeployKeyManager(self) """See :class:`~gitlab.v4.objects.DeployKeyManager`""" self.deploytokens = objects.DeployTokenManager(self) @@ -397,6 +399,11 @@ def lint(self, content: str, **kwargs: Any) -> Tuple[bool, List[str]]: Returns: (True, []) if the file is valid, (False, errors(list)) otherwise """ + utils.warn( + "`lint()` is deprecated and will be removed in a future version.\n" + "Please use `ci_lint.create()` instead.", + category=DeprecationWarning, + ) post_data = {"content": content} data = self.http_post("/ci/lint", post_data=post_data, **kwargs) if TYPE_CHECKING: diff --git a/gitlab/v4/objects/__init__.py b/gitlab/v4/objects/__init__.py index 40f9bf3fb..a390a4d5e 100644 --- a/gitlab/v4/objects/__init__.py +++ b/gitlab/v4/objects/__init__.py @@ -25,6 +25,7 @@ from .boards import * from .branches import * from .broadcast_messages import * +from .ci_lint import * from .clusters import * from .commits import * from .container_registry import * diff --git a/gitlab/v4/objects/ci_lint.py b/gitlab/v4/objects/ci_lint.py new file mode 100644 index 000000000..73f9d4d9d --- /dev/null +++ b/gitlab/v4/objects/ci_lint.py @@ -0,0 +1,45 @@ +""" +GitLab API: +https://docs.gitlab.com/ee/api/lint.html +""" + +from typing import Any, cast + +from gitlab.base import RESTManager, RESTObject +from gitlab.mixins import CreateMixin, GetWithoutIdMixin +from gitlab.types import RequiredOptional + +__all__ = [ + "CiLint", + "CiLintManager", + "ProjectCiLint", + "ProjectCiLintManager", +] + + +class CiLint(RESTObject): + _id_attr = None + + +class CiLintManager(CreateMixin, RESTManager): + _path = "/ci/lint" + _obj_cls = CiLint + _create_attrs = RequiredOptional( + required=("content",), optional=("include_merged_yaml", "include_jobs") + ) + + +class ProjectCiLint(RESTObject): + pass + + +class ProjectCiLintManager(GetWithoutIdMixin, CreateMixin, RESTManager): + _path = "/projects/{project_id}/ci/lint" + _obj_cls = ProjectCiLint + _from_parent_attrs = {"project_id": "id"} + _create_attrs = RequiredOptional( + required=("content",), optional=("dry_run", "include_jobs", "ref") + ) + + def get(self, **kwargs: Any) -> ProjectCiLint: + return cast(ProjectCiLint, super().get(**kwargs)) diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py index 367ab6892..ab3fb2052 100644 --- a/gitlab/v4/objects/projects.py +++ b/gitlab/v4/objects/projects.py @@ -1,7 +1,6 @@ """ GitLab API: https://docs.gitlab.com/ee/api/projects.html -https://docs.gitlab.com/ee/api/lint.html """ from typing import ( Any, @@ -39,6 +38,7 @@ from .badges import ProjectBadgeManager # noqa: F401 from .boards import ProjectBoardManager # noqa: F401 from .branches import ProjectBranchManager, ProjectProtectedBranchManager # noqa: F401 +from .ci_lint import ProjectCiLintManager # noqa: F401 from .clusters import ProjectClusterManager # noqa: F401 from .commits import ProjectCommitManager # noqa: F401 from .container_registry import ProjectRegistryRepositoryManager # noqa: F401 @@ -102,8 +102,6 @@ "ProjectRemoteMirrorManager", "ProjectStorage", "ProjectStorageManager", - "ProjectCiLint", - "ProjectCiLintManager", ] @@ -165,7 +163,7 @@ class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTO badges: ProjectBadgeManager boards: ProjectBoardManager branches: ProjectBranchManager - ci_lint: "ProjectCiLintManager" + ci_lint: ProjectCiLintManager clusters: ProjectClusterManager commits: ProjectCommitManager customattributes: ProjectCustomAttributeManager @@ -1063,18 +1061,3 @@ 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_ci_lint.py b/tests/unit/objects/test_ci_lint.py new file mode 100644 index 000000000..509a5ed1b --- /dev/null +++ b/tests/unit/objects/test_ci_lint.py @@ -0,0 +1,71 @@ +import pytest +import responses + +gitlab_ci_yml = """--- +:test_job: + :script: echo 1 +""" + +ci_lint_create_content = {"status": "valid", "errors": [], "warnings": []} + + +project_ci_lint_content = { + "valid": True, + "merged_yaml": "---\n:test_job:\n :script: echo 1\n", + "errors": [], + "warnings": [], +} + + +@pytest.fixture +def resp_create_ci_lint(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.POST, + url="http://localhost/api/v4/ci/lint", + json=ci_lint_create_content, + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_get_project_ci_lint(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.GET, + url="http://localhost/api/v4/projects/1/ci/lint", + json=project_ci_lint_content, + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_create_project_ci_lint(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.POST, + url="http://localhost/api/v4/projects/1/ci/lint", + json=project_ci_lint_content, + content_type="application/json", + status=200, + ) + yield rsps + + +def test_ci_lint_create(gl, resp_create_ci_lint): + lint_result = gl.ci_lint.create({"content": gitlab_ci_yml}) + assert lint_result.status == "valid" + + +def test_project_ci_lint_get(project, resp_get_project_ci_lint): + lint_result = project.ci_lint.get() + assert lint_result.valid is True + + +def test_project_ci_lint_create(project, resp_create_project_ci_lint): + lint_result = project.ci_lint.create({"content": gitlab_ci_yml}) + assert lint_result.valid is True diff --git a/tests/unit/objects/test_projects.py b/tests/unit/objects/test_projects.py index 4db88356a..85bae8600 100644 --- a/tests/unit/objects/test_projects.py +++ b/tests/unit/objects/test_projects.py @@ -82,12 +82,6 @@ "status": "created", "source": "trigger", } -ci_lint_get_content = { - "valid": True, - "merged_yaml": "---\n:test_job:\n :script: echo 1\n", - "errors": [], - "warnings": [], -} @pytest.fixture @@ -547,32 +541,6 @@ 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) @@ -788,17 +756,3 @@ 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