From 4607a2e3fe599b225a1496cb785cbaaac502bc28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Thu, 12 Oct 2023 16:52:47 +0200 Subject: [PATCH] Add: Extend GitHub API for code scanning default setup Implement the code scanning default setup API of GitHub. --- pontos/github/api/code_scanning.py | 97 ++++++++++++++++++++++- pontos/github/models/code_scanning.py | 55 +++++++++++++ tests/github/api/test_code_scanning.py | 57 +++++++++++++ tests/github/models/test_code_scanning.py | 26 ++++++ 4 files changed, 234 insertions(+), 1 deletion(-) diff --git a/pontos/github/api/code_scanning.py b/pontos/github/api/code_scanning.py index 9a9732a9..8287b376 100644 --- a/pontos/github/api/code_scanning.py +++ b/pontos/github/api/code_scanning.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -from typing import AsyncIterator, Optional, Union +from typing import AsyncIterator, Iterable, Optional, Union from pontos.github.api.client import GitHubAsyncREST from pontos.github.models.base import SortOrder @@ -12,8 +12,12 @@ Analysis, CodeQLDatabase, CodeScanningAlert, + DefaultSetup, + DefaultSetupState, DismissedReason, Instance, + Language, + QuerySuite, Severity, ) from pontos.helper import enum_or_value @@ -535,3 +539,94 @@ async def codeql_database( response = await self._client.get(api) response.raise_for_status() return CodeQLDatabase.from_dict(response.json()) + + async def default_setup( + self, + repo: str, + ) -> DefaultSetup: + """ + Gets a code scanning default setup configuration + + https://docs.github.com/en/rest/code-scanning/code-scanning#get-a-code-scanning-default-setup-configuration + + Args: + repo: GitHub repository (owner/name) + + Raises: + HTTPStatusError: A httpx.HTTPStatusError is raised if the request + failed. + + Returns: + Code scanning default setup + + Example: + .. code-block:: python + + from pontos.github.api import GitHubAsyncRESTApi + + async with GitHubAsyncRESTApi(token) as api: + setup = await api.code_scanning.default_setup( + "org/repo" + ) + print(setup) + """ + + api = f"/repos/{repo}/code-scanning/default-setup" + response = await self._client.get(api) + response.raise_for_status() + return DefaultSetup.from_dict(response.json()) + + async def update_default_setup( + self, + repo: str, + state: Union[str, DefaultSetupState], + query_suite: Union[str, QuerySuite], + languages: Iterable[Union[str, Language]], + ) -> dict[str, str]: + """ + Updates a code scanning default setup configuration + + https://docs.github.com/en/rest/code-scanning/code-scanning#update-a-code-scanning-default-setup-configuration + + Args: + repo: GitHub repository (owner/name) + state: Whether code scanning default setup has been configured or + not + query_suite: CodeQL query suite to be used + languages: CodeQL languages to be analyzed + + Raises: + HTTPStatusError: A httpx.HTTPStatusError is raised if the request + failed. + + Returns: + See the GitHub documentation for the response object + + Example: + .. code-block:: python + + from pontos.github.api import GitHubAsyncRESTApi + from pontos.github.models.code_scanning import ( + DefaultSetupState, + Language, + QuerySuite, + ) + + async with GitHubAsyncRESTApi(token) as api: + await api.code_scanning.update_default_setup( + "org/repo", + state=DefaultSetupState.CONFIGURED, + query_suite=QuerySuite.EXTENDED, + languages=[Language.PYTHON, Language.JAVASCRIPT] + ) + """ + + api = f"/repos/{repo}/code-scanning/code-scanning/default-setup" + data = { + "state": enum_or_value(state), + "query_suite": enum_or_value(query_suite), + "languages": [enum_or_value(value) for value in languages], + } + response = await self._client.patch(api, data=data) + response.raise_for_status() + return response.json() diff --git a/pontos/github/models/code_scanning.py b/pontos/github/models/code_scanning.py index 84cd3726..571c32de 100644 --- a/pontos/github/models/code_scanning.py +++ b/pontos/github/models/code_scanning.py @@ -313,3 +313,58 @@ class CodeQLDatabase(GitHubModel): updated_at: datetime url: str commit_oid: Optional[str] = None + + +class DefaultSetupState(Enum): + """ + State of a default setup + """ + + CONFIGURED = "configured" + NOT_CONFIGURED = "not-configured" + + +class Language(Enum): + """ + Analyzed Language + """ + + C_CPP = "c-cpp" + CSHARP = "csharp" + GO = "go" + JAVA_KOTLIN = "java-kotlin" + JAVASCRIPT_TYPESCRIPT = "javascript-typescript" + JAVASCRIPT = "javascript" + PYTHON = "python" + RUBY = "ruby" + TYPESCRIPT = "typescript" + SWIFT = "swift" + + +class QuerySuite(Enum): + """ + Used code scanning query suite + """ + + DEFAULT = "default" + EXTENDED = "extended" + + +@dataclass +class DefaultSetup(GitHubModel): + """ + Code scanning default setup configuration + + Attributes: + state: Code scanning default setup has been configured or not + languages: Languages to be analyzed + query_suite: CodeQL query suite to be used + updated_at: Timestamp of latest configuration update + schedule: The frequency of the periodic analysis + """ + + state: DefaultSetupState + languages: list[Language] + query_suite: QuerySuite + updated_at: Optional[datetime] = None + schedule: Optional[str] = None diff --git a/tests/github/api/test_code_scanning.py b/tests/github/api/test_code_scanning.py index 8920afce..47567c67 100644 --- a/tests/github/api/test_code_scanning.py +++ b/tests/github/api/test_code_scanning.py @@ -9,7 +9,10 @@ from pontos.github.models.code_scanning import ( AlertSort, AlertState, + DefaultSetupState, DismissedReason, + Language, + QuerySuite, Severity, ) from tests import AsyncIteratorMock, aiter, anext @@ -1194,3 +1197,57 @@ async def test_codeql_database(self): ) self.assertEqual(alert.id, 1) + + async def test_default_setup(self): + response = create_response() + response.json.return_value = { + "state": "configured", + "languages": ["ruby", "python"], + "query_suite": "default", + "updated_at": "2023-01-19T11:21:34Z", + "schedule": "weekly", + } + self.client.get.return_value = response + + setup = await self.api.default_setup( + "foo/bar", + ) + + self.client.get.assert_awaited_once_with( + "/repos/foo/bar/code-scanning/default-setup", + ) + + self.assertEqual(setup.state, DefaultSetupState.CONFIGURED) + + async def test_update_default_setup(self): + response = create_response() + response.json.return_value = { + "run_id": 42, + "run_url": "https://api.github.com/repos/octoorg/octocat/actions/runs/42", + } + self.client.patch.return_value = response + + resp = await self.api.update_default_setup( + "foo/bar", + state=DefaultSetupState.CONFIGURED, + query_suite=QuerySuite.EXTENDED, + languages=[Language.GO], + ) + + self.client.patch.assert_awaited_once_with( + "/repos/foo/bar/code-scanning/code-scanning/default-setup", + data={ + "state": "configured", + "query_suite": "extended", + "languages": ["go"], + }, + ) + + self.assertEqual( + resp["run_id"], + 42, + ) + self.assertEqual( + resp["run_url"], + "https://api.github.com/repos/octoorg/octocat/actions/runs/42", + ) diff --git a/tests/github/models/test_code_scanning.py b/tests/github/models/test_code_scanning.py index 2dcc1128..580e50cb 100644 --- a/tests/github/models/test_code_scanning.py +++ b/tests/github/models/test_code_scanning.py @@ -12,8 +12,12 @@ Analysis, CodeQLDatabase, CodeScanningAlert, + DefaultSetup, + DefaultSetupState, Instance, + Language, Location, + QuerySuite, Rule, Severity, Tool, @@ -401,3 +405,25 @@ def test_from_dict(self): "https://api.github.com/repos/octocat/Hello-World/code-scanning/codeql/databases/java", ) self.assertEqual(db.commit_oid, "12345678901234567000") + + +class DefaultSetupTestCase(unittest.TestCase): + def test_from_dict(self): + setup = DefaultSetup.from_dict( + { + "state": "configured", + "languages": ["ruby", "python"], + "query_suite": "default", + "updated_at": "2023-01-19T11:21:34Z", + "schedule": "weekly", + } + ) + + self.assertEqual(setup.state, DefaultSetupState.CONFIGURED) + self.assertEqual(setup.languages, [Language.RUBY, Language.PYTHON]) + self.assertEqual(setup.query_suite, QuerySuite.DEFAULT) + self.assertEqual( + setup.updated_at, + datetime(2023, 1, 19, 11, 21, 34, tzinfo=timezone.utc), + ) + self.assertEqual(setup.schedule, "weekly")