From 40539a94ddec3775301dd36df4dc0f43edfd8b6a Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Tue, 5 Jul 2022 11:48:55 +0200 Subject: [PATCH 1/4] refactor(objects): move ci lint to separate file --- gitlab/v4/objects/__init__.py | 1 + gitlab/v4/objects/ci_lint.py | 22 +++++++++++++ gitlab/v4/objects/projects.py | 17 +--------- tests/unit/objects/test_ci_lint.py | 49 +++++++++++++++++++++++++++++ tests/unit/objects/test_projects.py | 46 --------------------------- 5 files changed, 73 insertions(+), 62 deletions(-) create mode 100644 gitlab/v4/objects/ci_lint.py create mode 100644 tests/unit/objects/test_ci_lint.py 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..3d5d488f5 --- /dev/null +++ b/gitlab/v4/objects/ci_lint.py @@ -0,0 +1,22 @@ +""" +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 + + +class ProjectCiLint(RESTObject): + pass + + +class ProjectCiLintManager(GetWithoutIdMixin, CreateMixin, RESTManager): + _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/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py index 367ab6892..9d7262f7b 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 ProjectCiLint, ProjectCiLintManager # noqa: F401 from .clusters import ProjectClusterManager # noqa: F401 from .commits import ProjectCommitManager # noqa: F401 from .container_registry import ProjectRegistryRepositoryManager # noqa: F401 @@ -1063,18 +1063,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..65911980a --- /dev/null +++ b/tests/unit/objects/test_ci_lint.py @@ -0,0 +1,49 @@ +import pytest +import responses + +ci_lint_get_content = { + "valid": True, + "merged_yaml": "---\n:test_job:\n :script: echo 1\n", + "errors": [], + "warnings": [], +} + + +@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_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 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 From 47bfd769e9cbe9ee71f5dba36089aae0ca6eda8d Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Tue, 5 Jul 2022 11:51:56 +0200 Subject: [PATCH 2/4] chore(ci_lint): add create attributes --- gitlab/v4/objects/ci_lint.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gitlab/v4/objects/ci_lint.py b/gitlab/v4/objects/ci_lint.py index 3d5d488f5..4f781dcac 100644 --- a/gitlab/v4/objects/ci_lint.py +++ b/gitlab/v4/objects/ci_lint.py @@ -7,6 +7,7 @@ from gitlab.base import RESTManager, RESTObject from gitlab.mixins import CreateMixin, GetWithoutIdMixin +from gitlab.types import RequiredOptional class ProjectCiLint(RESTObject): @@ -17,6 +18,9 @@ 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)) From 2eb6d7e38360cc578e493d5e0bdfdde9a7488454 Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Tue, 5 Jul 2022 12:17:04 +0200 Subject: [PATCH 3/4] feat(cli): add support for global CI lint --- gitlab/client.py | 7 +++++ gitlab/v4/objects/ci_lint.py | 19 +++++++++++++ gitlab/v4/objects/projects.py | 6 ++-- tests/unit/objects/test_ci_lint.py | 44 ++++++++++++++++++++++-------- 4 files changed, 61 insertions(+), 15 deletions(-) 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/ci_lint.py b/gitlab/v4/objects/ci_lint.py index 4f781dcac..73f9d4d9d 100644 --- a/gitlab/v4/objects/ci_lint.py +++ b/gitlab/v4/objects/ci_lint.py @@ -9,6 +9,25 @@ 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 diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py index 9d7262f7b..ab3fb2052 100644 --- a/gitlab/v4/objects/projects.py +++ b/gitlab/v4/objects/projects.py @@ -38,7 +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 ProjectCiLint, ProjectCiLintManager # 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 diff --git a/tests/unit/objects/test_ci_lint.py b/tests/unit/objects/test_ci_lint.py index 65911980a..509a5ed1b 100644 --- a/tests/unit/objects/test_ci_lint.py +++ b/tests/unit/objects/test_ci_lint.py @@ -1,7 +1,15 @@ import pytest import responses -ci_lint_get_content = { +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": [], @@ -10,12 +18,25 @@ @pytest.fixture -def resp_get_ci_lint(): +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=ci_lint_get_content, + json=project_ci_lint_content, content_type="application/json", status=200, ) @@ -23,27 +44,28 @@ def resp_get_ci_lint(): @pytest.fixture -def resp_create_ci_lint(): +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=ci_lint_get_content, + json=project_ci_lint_content, content_type="application/json", status=200, ) yield rsps -def test_project_ci_lint_get(project, resp_get_ci_lint): +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_ci_lint): - gitlab_ci_yml = """--- -:test_job: - :script: echo 1 -""" +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 From 45e99eb8c86303791e6a78468abc364f6911a8f5 Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Tue, 5 Jul 2022 14:14:40 +0200 Subject: [PATCH 4/4] docs: document CI Lint usage --- docs/api-objects.rst | 1 + docs/cli-examples.rst | 42 ++++++++++++++++++++++++++++ docs/cli-usage.rst | 2 ++ docs/gl_objects/ci_lint.rst | 53 ++++++++++++++++++++++++++++++++++++ docs/gl_objects/projects.rst | 43 ----------------------------- 5 files changed, 98 insertions(+), 43 deletions(-) create mode 100644 docs/gl_objects/ci_lint.rst 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 =============================