diff --git a/Makefile b/Makefile index 0001a10..f218515 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ push: build docker push $(IMG):$(TAG) test: - ENV="test" pytest + pytest qa: isort --profile black . && black . && flake8 diff --git a/dev-requirements.txt b/dev-requirements.txt index ed61716..4b8b8f4 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -9,4 +9,5 @@ pytest pytest-mock types-python-slugify types-PyYAML -types-requests \ No newline at end of file +types-requests +pytest-env==1.1.5 diff --git a/gitlab2sentry/__init__.py b/gitlab2sentry/__init__.py index d215f53..24068a5 100644 --- a/gitlab2sentry/__init__.py +++ b/gitlab2sentry/__init__.py @@ -7,20 +7,11 @@ from gitlab2sentry.exceptions import SentryProjectCreationFailed from gitlab2sentry.resources import ( - DSN_MR_TITLE, G2S_STATS, - GITLAB_GRAPHQL_SUFFIX, - GITLAB_GROUP_IDENTIFIER, - GITLAB_TOKEN, - GITLAB_URL, GRAPHQL_FETCH_PROJECT_QUERY, GRAPHQL_LIST_PROJECTS_QUERY, - SENTRY_ORG_SLUG, - SENTRY_TOKEN, - SENTRY_URL, - SENTRYCLIRC_FILEPATH, - SENTRYCLIRC_MR_TITLE, G2SProject, + settings, ) from gitlab2sentry.utils import GitlabProvider, SentryProvider @@ -43,10 +34,12 @@ def __str__(self) -> str: return "" def _get_gitlab_provider(self) -> GitlabProvider: - return GitlabProvider(GITLAB_URL, GITLAB_TOKEN) + return GitlabProvider(settings.gitlab_url, settings.gitlab_token) def _get_sentry_provider(self) -> SentryProvider: - return SentryProvider(SENTRY_URL, SENTRY_TOKEN, SENTRY_ORG_SLUG) + return SentryProvider( + settings.sentry_url, settings.sentry_token, settings.sentry_org_slug + ) def _ensure_sentry_group(self, name: str) -> None: if name not in self.sentry_groups: @@ -118,12 +111,14 @@ def _get_mr_states( sentryclirc_mr_state, dsn_mr_state = None, None if mr_list: for mr in mr_list: - if mr["title"] == SENTRYCLIRC_MR_TITLE.format( + if mr["title"] == settings.sentryclirc_mr_title.format( project_name=project_name ): if not (sentryclirc_mr_state and sentryclirc_mr_state == "opened"): sentryclirc_mr_state = mr["state"] - elif mr["title"] == DSN_MR_TITLE.format(project_name=project_name): + elif mr["title"] == settings.dsn_mr_title.format( + project_name=project_name + ): if not (dsn_mr_state and dsn_mr_state == "opened"): dsn_mr_state = mr["state"] else: @@ -138,7 +133,7 @@ def _is_group_project(self, group: Optional[Dict[str, Any]]) -> bool: def _get_sentryclirc_file(self, blob: List[Dict[str, Any]]) -> tuple: has_sentryclirc_file, has_dsn = False, False - if blob and blob[0]["name"] == SENTRYCLIRC_FILEPATH: + if blob and blob[0]["name"] == settings.sentryclirc_filepath: has_sentryclirc_file = True if blob[0].get("rawTextBlob"): for line in blob[0]["rawTextBlob"].split("\n"): @@ -191,7 +186,7 @@ def _get_paginated_projects(self) -> List[Dict[str, Any]]: query_start_time = time.time() logging.info( "{}: Starting querying all Gitlab group-projects with Graphql at {}/{}".format( # noqa - self.__str__(), GITLAB_URL, GITLAB_GRAPHQL_SUFFIX + self.__str__(), settings.gitlab_url, settings.gitlab_graphql_suffix ) ) request_gen = self.gitlab_provider.get_all_projects(GRAPHQL_LIST_PROJECTS_QUERY) @@ -209,7 +204,7 @@ def _get_gitlab_project(self, full_path: str) -> Optional[G2SProject]: GRAPHQL_FETCH_PROJECT_QUERY["full_path"] = full_path logging.info( "{}: Starting querying for specific Gitlab project with Graphql at {}/{}".format( # noqa - self.__str__(), GITLAB_URL, GITLAB_GRAPHQL_SUFFIX + self.__str__(), settings.gitlab_url, settings.gitlab_graphql_suffix ) ) result = self.gitlab_provider.get_project(GRAPHQL_FETCH_PROJECT_QUERY) @@ -227,7 +222,7 @@ def _get_gitlab_groups(self): result = result_node["node"] if self._is_group_project(result["group"]): group_name = result["fullPath"].split("/")[0] - if group_name.startswith(GITLAB_GROUP_IDENTIFIER): + if group_name.startswith(settings.gitlab_group_identifier): g2s_project = self._get_g2s_project(result) if g2s_project: @@ -245,7 +240,7 @@ def _create_sentry_project( full_path: str, sentry_group_name: str, sentry_project_name: str, - sentry_project_slug: str + sentry_project_slug: str, ) -> Optional[Dict[str, Any]]: try: return self.sentry_provider.get_or_create_project( @@ -271,7 +266,7 @@ def _handle_g2s_project( self, g2s_project: G2SProject, sentry_group_name: str, - custom_name: Optional[str] = None + custom_name: Optional[str] = None, ) -> bool: """ Creates sentry project for all given gitlab projects. It @@ -319,7 +314,7 @@ def _handle_g2s_project( g2s_project.full_path, sentry_group_name, sentry_project_name, - sentry_project_slug + sentry_project_slug, ) # If Sentry fails to create project skip @@ -363,9 +358,7 @@ def _handle_g2s_project( return False def update( - self, - full_path: Optional[str] = None, - custom_name: Optional[str] = None + self, full_path: Optional[str] = None, custom_name: Optional[str] = None ) -> None: """ args: full_path @@ -387,7 +380,7 @@ def update( if g2s_project: sentry_group_name = g2s_project.group.split("/")[0].strip() self._handle_g2s_project( - g2s_project, sentry_group_name, custom_name # type: ignore + g2s_project, sentry_group_name, custom_name # type: ignore ) else: logging.info( diff --git a/gitlab2sentry/resources.py b/gitlab2sentry/resources.py index 83e1188..059023d 100644 --- a/gitlab2sentry/resources.py +++ b/gitlab2sentry/resources.py @@ -1,183 +1,69 @@ -import logging import os from collections import namedtuple from typing import List, Tuple -from tests.resources import ( - TEST_DSN_BRANCH_NAME, - TEST_DSN_MR_CONTENT, - TEST_DSN_MR_DESCRIPTION, - TEST_DSN_MR_TITLE, - TEST_GITLAB_AUTHOR_EMAIL, - TEST_GITLAB_AUTHOR_NAME, - TEST_GITLAB_GRAPHQL_PAGE_LENGTH, - TEST_GITLAB_GRAPHQL_SUFFIX, - TEST_GITLAB_GRAPHQL_TIMEOUT, - TEST_GITLAB_MR_KEYWORD, - TEST_GITLAB_MR_LABEL_LIST, - TEST_GITLAB_RMV_SRC_BRANCH, - TEST_GITLAB_TOKEN, - TEST_GITLAB_URL, - TEST_SENTRY_DSN, - TEST_SENTRY_ORG_SLUG, - TEST_SENTRY_TOKEN, - TEST_SENTRY_URL, - TEST_SENTRYCLIRC_BRANCH_NAME, - TEST_SENTRYCLIRC_COM_MSG, - TEST_SENTRYCLIRC_FILEPATH, - TEST_SENTRYCLIRC_MR_CONTENT, - TEST_SENTRYCLIRC_MR_DESCRIPTION, - TEST_SENTRYCLIRC_MR_TITLE, -) - +from pydantic import Field +from pydantic_settings import BaseSettings -def is_test_env(env: str) -> bool: - return env == "test" +ENV = os.getenv("ENV", "production") -try: - ENV = os.getenv("ENV", "production") - # Sentry configuration - SENTRY_URL = ( - TEST_SENTRY_URL - if is_test_env(ENV) - else os.environ["SENTRY_URL"] - ) - SENTRY_TOKEN = ( - TEST_SENTRY_TOKEN - if is_test_env(ENV) - else os.environ["SENTRY_TOKEN"] - ) - SENTRY_DSN = ( - TEST_SENTRY_DSN - if is_test_env(ENV) - else os.environ["SENTRY_DSN"] - ) - SENTRY_ENV = os.getenv("SENTRY_ENV", "production") - SENTRY_ORG_SLUG = ( - TEST_SENTRY_ORG_SLUG - if is_test_env(ENV) - else os.environ["SENTRY_ORG_SLUG"] - ) - # DSN MR configuration. - DSN_MR_CONTENT = ( - TEST_DSN_MR_CONTENT - if is_test_env(ENV) - else os.environ["GITLAB_DSN_MR_CONTENT"] - ) - DSN_BRANCH_NAME = ( - TEST_DSN_BRANCH_NAME - if is_test_env(ENV) - else os.environ["GITLAB_DSN_MR_BRANCH_NAME"] - ) - DSN_MR_TITLE = ( - TEST_DSN_MR_TITLE - if is_test_env(ENV) - else os.environ["GITLAB_DSN_MR_TITLE"] - ) - DSN_MR_DESCRIPTION = ( - TEST_DSN_MR_DESCRIPTION - if is_test_env(ENV) - else os.environ["GITLAB_DSN_MR_DESCRIPTION"] +class Settings(BaseSettings): + dsn_branch_name: str = Field("auto_add_sentry_dsn") + dsn_mr_content: str = Field( + """ + ## File generated by gitlab2sentry + [defaults] + url = {sentry_url} + dsn = {dsn} + project = {project_slug} + """ + ) + dsn_mr_description: str = Field( + """{mentions} Congrats, your Sentry project has been created, merge this to finalize your Sentry integration of {name_with_namespace} :clap: :cookie:""" # noqa + ) + dsn_mr_title: str = Field( + "[gitlab2sentry] Merge me to add your Sentry DSN to {project_name}" + ) + env: str = Field("production") + gitlab_author_email: str = Field("default-email@example.com") + gitlab_author_name: str = Field("Default Author") + gitlab_graphql_page_length: int = Field(0) + gitlab_graphql_suffix: str = Field("default-content") + gitlab_graphql_timeout: int = Field(10) + gitlab_group_identifier: str = Field("") + gitlab_mentions: str = Field("", examples=["@foo,@bar"]) + gitlab_mentions_access_level: int = Field(40) + gitlab_mr_keyword: str = Field("sentry") + gitlab_mr_label_list: List[str] = Field(["sentry"]) + gitlab_project_creation_limit: int = Field(30) + gitlab_rmv_src_branch: bool = Field(True) + gitlab_token: str = Field("default-token") + gitlab_url: str = Field("http://default-gitlab-url") + sentry_dsn: str = Field("http://default.sentry.com") + sentry_env: str = Field("production") + sentry_org_slug: str = Field("default_org") + sentry_token: str = Field("default-token") + sentry_url: str = Field("http://default-sentry-url") + sentryclirc_branch_name: str = Field("auto_add_sentry") + sentryclirc_com_msg: str = Field("Update .sentryclirc") + sentryclirc_filepath: str = Field(".sentryclirc") + sentryclirc_mr_content: str = Field( + """ + ## File generated by gitlab2sentry + [defaults] + url = {sentry_url} + """ + ) + sentryclirc_mr_description: str = Field( + """{mentions} Merge this and it will automatically create a Sentry project for {name_with_namespace} :cookie:""" # noqa + ) + sentryclirc_mr_title: str = Field( + """"[gitlab2sentry] Merge me to add Sentry to {project_name} or close me""" ) - # Sentryclirc MR configuration. - SENTRYCLIRC_MR_CONTENT = ( - TEST_SENTRYCLIRC_MR_CONTENT - if is_test_env(ENV) - else os.environ["GITLAB_SENTRYCLIRC_MR_CONTENT"] - ) - SENTRYCLIRC_BRANCH_NAME = ( - TEST_SENTRYCLIRC_BRANCH_NAME - if is_test_env(ENV) - else os.environ["GITLAB_SENTRYCLIRC_MR_BRANCH_NAME"] - ) - SENTRYCLIRC_MR_TITLE = ( - TEST_SENTRYCLIRC_MR_TITLE - if is_test_env(ENV) - else os.environ["GITLAB_SENTRYCLIRC_MR_TITLE"] - ) - SENTRYCLIRC_FILEPATH = ( - TEST_SENTRYCLIRC_FILEPATH - if is_test_env(ENV) - else os.environ["GITLAB_SENTRYCLIRC_MR_FILEPATH"] - ) - SENTRYCLIRC_COM_MSG = ( - TEST_SENTRYCLIRC_COM_MSG - if is_test_env(ENV) - else os.environ["GITLAB_SENTRYCLIRC_MR_COMMIT_MSG"] - ) - SENTRYCLIRC_MR_DESCRIPTION = ( - TEST_SENTRYCLIRC_MR_DESCRIPTION - if is_test_env(ENV) - else os.environ["GITLAB_SENTRYCLIRC_MR_DESCRIPTION"] - ) - # Gitlab Configuration. - GITLAB_URL = ( - TEST_GITLAB_URL - if is_test_env(ENV) - else os.environ["GITLAB_URL"] - ) - GITLAB_TOKEN = ( - TEST_GITLAB_TOKEN - if is_test_env(ENV) - else os.environ["GITLAB_TOKEN"] - ) - GITLAB_GRAPHQL_SUFFIX = ( - TEST_GITLAB_GRAPHQL_SUFFIX - if is_test_env(ENV) - else os.environ["GITLAB_GRAPHQL_SUFFIX"] - ) - GITLAB_GRAPHQL_TIMEOUT = ( - TEST_GITLAB_GRAPHQL_TIMEOUT - if is_test_env(ENV) - else int(os.environ["GITLAB_AIOHTTP_TIMEOUT"]) - ) - GITLAB_GRAPHQL_PAGE_LENGTH = ( - TEST_GITLAB_GRAPHQL_PAGE_LENGTH - if is_test_env(ENV) - else int(os.environ["GITLAB_GRAPHQL_PAGE_LENGTH"]) - ) - GITLAB_GROUP_IDENTIFIER = os.getenv("GITLAB_GROUP_IDENTIFIER", "") - GITLAB_AUTHOR_EMAIL = ( - TEST_GITLAB_AUTHOR_EMAIL - if is_test_env(ENV) - else os.environ["GITLAB_AUTHOR_EMAIL"] - ) - GITLAB_AUTHOR_NAME = ( - TEST_GITLAB_AUTHOR_NAME - if is_test_env(ENV) - else os.environ["GITLAB_AUTHOR_NAME"] - ) - GITLAB_PROJECT_CREATION_LIMIT = int(os.getenv("GITLAB_CREATION_DAYS_LIMIT", 30)) - GITLAB_RMV_SRC_BRANCH = ( - TEST_GITLAB_RMV_SRC_BRANCH - if is_test_env(ENV) - else bool(int(os.environ["GITLAB_REMOVE_SOURCE"])) - ) - GITLAB_MENTIONS_LIST = os.getenv("GITLAB_MENTIONS") - GITLAB_MENTIONS_ACCESS_LEVEL = int(os.getenv("GITLAB_MENTIONS_ACCESS_LEVEL", 40)) - GITLAB_MR_KEYWORD = ( - TEST_GITLAB_MR_KEYWORD - if is_test_env(ENV) - else os.environ["GITLAB_MR_KEYWORD"] - ) - GITLAB_MR_LABEL_LIST = ( - TEST_GITLAB_MR_LABEL_LIST - if is_test_env(ENV) - else os.environ.get("GITLAB_MR_LABEL_LIST", "").split(",") - ) -except KeyError as key_error: - logging.error( - ": env vars are not configured properly - {}".format(str(key_error)) - ) - exit(1) -except ValueError as value_error: - logging.error( - ": env vars have wrong value type - {}".format(str(value_error)) - ) - exit(1) +settings = Settings() # type: ignore # G2SProject namedtuple configuration G2SProject = namedtuple( @@ -285,36 +171,3 @@ def is_test_env(env: str) -> bool: } """, } -GRAPHQL_TEST_QUERY = { - "name": "TEST_QUERY", - "instance": "projects", - "body": """ -{ - project(fullPath: "none") { - id - fullPath - name - createdAt - mergeRequestsEnabled - group { - name - } - repository { - blobs { - nodes { - name - rawTextBlob - } - } - } - mergeRequests { - nodes { - id - title - state - } - } - } -} -""", -} diff --git a/gitlab2sentry/utils/gitlab_provider.py b/gitlab2sentry/utils/gitlab_provider.py index 1d810b9..343fba7 100644 --- a/gitlab2sentry/utils/gitlab_provider.py +++ b/gitlab2sentry/utils/gitlab_provider.py @@ -11,42 +11,19 @@ from gql.transport.aiohttp import AIOHTTPTransport from gql.transport.aiohttp import log as websockets_logger -from gitlab2sentry.resources import ( - DSN_BRANCH_NAME, - DSN_MR_CONTENT, - DSN_MR_TITLE, - ENV, - GITLAB_AUTHOR_EMAIL, - GITLAB_AUTHOR_NAME, - GITLAB_GRAPHQL_PAGE_LENGTH, - GITLAB_GRAPHQL_SUFFIX, - GITLAB_GRAPHQL_TIMEOUT, - GITLAB_MENTIONS_ACCESS_LEVEL, - GITLAB_MENTIONS_LIST, - GITLAB_MR_LABEL_LIST, - GITLAB_PROJECT_CREATION_LIMIT, - GITLAB_RMV_SRC_BRANCH, - GITLAB_TOKEN, - GITLAB_URL, - SENTRY_URL, - SENTRYCLIRC_BRANCH_NAME, - SENTRYCLIRC_COM_MSG, - SENTRYCLIRC_FILEPATH, - SENTRYCLIRC_MR_CONTENT, - SENTRYCLIRC_MR_DESCRIPTION, - SENTRYCLIRC_MR_TITLE, - G2SProject, -) +from gitlab2sentry.resources import G2SProject, settings class GraphQLClient: def __init__( - self, url: Optional[str] = GITLAB_URL, token: Optional[str] = GITLAB_TOKEN + self, + url: Optional[str] = settings.gitlab_url, + token: Optional[str] = settings.gitlab_token, ): self._client = Client( transport=self._get_transport(url, token), fetch_schema_from_transport=True, - execute_timeout=GITLAB_GRAPHQL_TIMEOUT, + execute_timeout=settings.gitlab_graphql_timeout, ) websockets_logger.setLevel(logging.WARNING) @@ -57,7 +34,7 @@ def _get_transport( self, url: Optional[str], token: Optional[str] ) -> AIOHTTPTransport: return AIOHTTPTransport( - url="{}/{}".format(url, GITLAB_GRAPHQL_SUFFIX), + url="{}/{}".format(url, settings.gitlab_graphql_suffix), headers={ "PRIVATE-TOKEN": token, # type: ignore "Content-Type": "application/json", @@ -80,9 +57,9 @@ def _query(self, name: str, query: str) -> Dict[str, Any]: def project_fetch_query(self, query_dict: Dict[str, str]) -> Dict[str, Any]: project_full_path = f"{query_dict['full_path']}" - blobsPaths = '(paths: "{}")'.format(SENTRYCLIRC_FILEPATH) + blobsPaths = '(paths: "{}")'.format(settings.sentryclirc_filepath) titlesListMRs = '(sourceBranches: ["{}","{}"])'.format( - SENTRYCLIRC_BRANCH_NAME, DSN_BRANCH_NAME + settings.sentryclirc_branch_name, settings.dsn_branch_name ) query = query_dict["body"] % (project_full_path, blobsPaths, titlesListMRs) return self._query(query_dict["name"], query) @@ -92,13 +69,13 @@ def project_list_query( ) -> Dict[str, Any]: whereStatement = ' searchNamespaces: true sort: "createdAt_desc"' edgesStatement = "(first: {}{}{})".format( - GITLAB_GRAPHQL_PAGE_LENGTH, + settings.gitlab_graphql_page_length, f' after: "{endCursor}"' if endCursor else "", whereStatement, ) - blobsPaths = '(paths: "{}")'.format(SENTRYCLIRC_FILEPATH) + blobsPaths = '(paths: "{}")'.format(settings.sentryclirc_filepath) titlesListMRs = '(sourceBranches: ["{}","{}"])'.format( - SENTRYCLIRC_BRANCH_NAME, DSN_BRANCH_NAME + settings.sentryclirc_branch_name, settings.dsn_branch_name ) query = query_dict["body"] % (edgesStatement, blobsPaths, titlesListMRs) return self._query(query_dict["name"], query) @@ -106,7 +83,9 @@ def project_list_query( class GitlabProvider: def __init__( - self, url: Optional[str] = GITLAB_URL, token: Optional[str] = GITLAB_TOKEN + self, + url: Optional[str] = settings.gitlab_url, + token: Optional[str] = settings.gitlab_token, ) -> None: self.gitlab = self._get_gitlab(url, token) self._gql_client = GraphQLClient(url, token) @@ -117,13 +96,15 @@ def __str__(self) -> str: def _get_gitlab(self, url: Optional[str], token: Optional[str]) -> Gitlab: gitlab = Gitlab(url, private_token=token) - if ENV != "test": + if settings.env != "test": gitlab.auth() return gitlab def _get_update_limit(self) -> Optional[datetime]: - if GITLAB_PROJECT_CREATION_LIMIT: - return datetime.now() - timedelta(days=GITLAB_PROJECT_CREATION_LIMIT) + if settings.gitlab_project_creation_limit: + return datetime.now() - timedelta( + days=settings.gitlab_project_creation_limit + ) else: return None @@ -197,21 +178,21 @@ def _get_or_create_sentryclirc( try: f = project.files.get(file_path=file_path, ref=project.default_branch) f.content = content - f.save(branch=branch_name, commit_message=SENTRYCLIRC_COM_MSG) + f.save(branch=branch_name, commit_message=settings.sentryclirc_com_msg) except GitlabGetError: logging.info( "{}: [Creating] Project {} - File not found for project {}.".format( self.__str__(), - SENTRYCLIRC_FILEPATH, + settings.sentryclirc_filepath, full_path, ) ) f = project.files.create( { - "author_email": GITLAB_AUTHOR_EMAIL, - "author_name": GITLAB_AUTHOR_NAME, + "author_email": settings.gitlab_author_email, + "author_name": settings.gitlab_author_name, "branch": branch_name, - "commit_message": SENTRYCLIRC_COM_MSG, + "commit_message": settings.sentryclirc_com_msg, "content": content, "file_path": file_path, } @@ -223,7 +204,7 @@ def _get_default_mentions(self, project: Project) -> str: f"@{member.username}" for member in project.members_all.list(iterator=True) if ( - member.access_level >= GITLAB_MENTIONS_ACCESS_LEVEL + member.access_level >= settings.gitlab_mentions_access_level and member.state != "blocked" ) ] @@ -234,8 +215,8 @@ def _get_mr_description( ) -> str: mentions = ( self._get_default_mentions(project) - if not GITLAB_MENTIONS_LIST - else ", ".join(GITLAB_MENTIONS_LIST) + if not settings.gitlab_mentions + else ", ".join(settings.gitlab_mentions) ) return "\n".join( [ @@ -265,14 +246,14 @@ def _create_mr( { "description": self._get_mr_description( project, - SENTRYCLIRC_MR_DESCRIPTION, + settings.sentryclirc_mr_description, g2s_project.name_with_namespace, ), - "remove_source_branch": GITLAB_RMV_SRC_BRANCH, + "remove_source_branch": settings.gitlab_rmv_src_branch, "source_branch": branch_name, "target_branch": project.default_branch, "title": title, - "labels": GITLAB_MR_LABEL_LIST, + "labels": settings.gitlab_mr_label_list, } ) return True @@ -295,10 +276,10 @@ def create_sentryclirc_mr(self, g2s_project: G2SProject) -> bool: ) return self._create_mr( g2s_project, - SENTRYCLIRC_BRANCH_NAME, - SENTRYCLIRC_FILEPATH, - SENTRYCLIRC_MR_CONTENT.format(sentry_url=SENTRY_URL), - SENTRYCLIRC_MR_TITLE.format(project_name=g2s_project.name), + settings.sentryclirc_branch_name, + settings.sentryclirc_filepath, + settings.sentryclirc_mr_content.format(sentry_url=settings.sentry_url), + settings.sentryclirc_mr_title.format(project_name=g2s_project.name), ) def create_dsn_mr( @@ -311,10 +292,10 @@ def create_dsn_mr( ) return self._create_mr( g2s_project, - DSN_BRANCH_NAME, - SENTRYCLIRC_FILEPATH, - DSN_MR_CONTENT.format( - sentry_url=SENTRY_URL, dsn=dsn, project_slug=project_slug + settings.dsn_branch_name, + settings.sentryclirc_filepath, + settings.dsn_mr_content.format( + sentry_url=settings.sentry_url, dsn=dsn, project_slug=project_slug ), - DSN_MR_TITLE.format(project_name=g2s_project.name), + settings.dsn_mr_title.format(project_name=g2s_project.name), ) diff --git a/gitlab2sentry/utils/sentry_provider.py b/gitlab2sentry/utils/sentry_provider.py index c77c38e..11e04f4 100644 --- a/gitlab2sentry/utils/sentry_provider.py +++ b/gitlab2sentry/utils/sentry_provider.py @@ -10,14 +10,14 @@ SentryProjectCreationFailed, SentryProjectKeyIDNotFound, ) -from gitlab2sentry.resources import SENTRY_ORG_SLUG, SENTRY_TOKEN, SENTRY_URL +from gitlab2sentry.resources import settings class SentryAPIClient: def __init__( self, - base_url: Optional[str] = SENTRY_URL, - token: Optional[str] = SENTRY_TOKEN, + base_url: Optional[str] = settings.sentry_url, + token: Optional[str] = settings.sentry_token, ): self.base_url = base_url self.url = "{}/api/0/{}" @@ -50,7 +50,9 @@ def simple_request( return self._get_json(requests.post(url, data=data, headers=self.headers)) elif method == "put": if json_format: - return self._get_json(requests.put(url, json=data, headers=self.headers)) + return self._get_json( + requests.put(url, json=data, headers=self.headers) + ) return self._get_json(requests.put(url, data=data, headers=self.headers)) else: return self._get_json(requests.get(url, headers=self.headers)) @@ -59,9 +61,9 @@ def simple_request( class SentryProvider: def __init__( self, - url: Optional[str] = SENTRY_URL, - token: Optional[str] = SENTRY_TOKEN, - org_slug: Optional[str] = SENTRY_ORG_SLUG, + url: Optional[str] = settings.sentry_url, + token: Optional[str] = settings.sentry_token, + org_slug: Optional[str] = settings.sentry_org_slug, ): self.url = url self.org_slug = org_slug @@ -151,7 +153,7 @@ def set_rate_limit_for_key(self, project_slug: str) -> Optional[str]: "put", "projects/{}/{}/keys/{}/".format(self.org_slug, project_slug, key), {"rateLimit": {"window": 60, "count": 300}}, - json_format=True + json_format=True, ) except SentryProjectKeyIDNotFound as key_id_err: logging.warning( diff --git a/pyproject.toml b/pyproject.toml index 32f3e3b..ca62781 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,3 +4,14 @@ tag_format = "$version" version_scheme = "semver" version = "1.0.2" update_changelog_on_bump = true + +[tool.pytest_env] +ENV = "test" +SENTRY_ENV = "test" +DSN_MR_CONTENT = """ +## File generated by gitlab2sentry +[defaults] +url = {sentry_url} +dsn = {dsn} +project = {project_slug} +""" diff --git a/requirements.txt b/requirements.txt index a95acac..3dc35d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,8 @@ aiohttp==3.10.5 awesome-slugify==1.6.5 gql==3.5.0 +pydantic-settings==2.5.2 +pydantic==2.9.2 python-gitlab==4.10.0 pytz==2022.1 requests==2.32.3 diff --git a/tests/conftest.py b/tests/conftest.py index 80a63c3..f8b9ee6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,26 +4,18 @@ import pytz from gitlab2sentry import Gitlab2Sentry -from gitlab2sentry.resources import ( - DSN_MR_CONTENT, - DSN_MR_TITLE, - GITLAB_GROUP_IDENTIFIER, - GITLAB_PROJECT_CREATION_LIMIT, - SENTRYCLIRC_FILEPATH, - SENTRYCLIRC_MR_CONTENT, - SENTRYCLIRC_MR_TITLE, - TEST_SENTRY_DSN, - TEST_SENTRY_URL, - G2SProject, -) +from gitlab2sentry.resources import G2SProject, settings from gitlab2sentry.utils.gitlab_provider import GitlabProvider, GraphQLClient from gitlab2sentry.utils.sentry_provider import SentryProvider TEST_PROJECT_NAME = "test" -TEST_GROUP_NAME = f"{GITLAB_GROUP_IDENTIFIER}test" +TEST_GROUP_NAME = f"{settings.gitlab_group_identifier}test" CURRENT_TIME = datetime.strftime(datetime.now(pytz.UTC), "%Y-%m-%dT%H:%M:%SZ") OLD_TIME = datetime.strftime( - (datetime.now(pytz.UTC) - timedelta(days=(GITLAB_PROJECT_CREATION_LIMIT + 1))), + ( + datetime.now(pytz.UTC) + - timedelta(days=(settings.gitlab_project_creation_limit + 1)) + ), "%Y-%m-%dT%H:%M:%SZ", ) @@ -57,7 +49,7 @@ def __init__(self, username, access_level, state): TEST_GITLAB_PROJECT_MEMBERS = [ TestGitlabMember("active_user", 40, "active"), - TestGitlabMember("blocked_user", 40, "blocked") + TestGitlabMember("blocked_user", 40, "blocked"), ] @@ -110,18 +102,18 @@ def create_graphql_json_object(**kwargs): if kwargs["has_sentryclirc_file"]: if kwargs["has_dsn"]: blob_item = { - "name": SENTRYCLIRC_FILEPATH, - "rawTextBlob": DSN_MR_CONTENT.format( - sentry_url=TEST_SENTRY_URL, - dsn=TEST_SENTRY_DSN, - project_slug=TEST_PROJECT_NAME + "name": settings.sentryclirc_filepath, + "rawTextBlob": settings.dsn_mr_content.format( + sentry_url=settings.sentry_url, + dsn=settings.sentry_dsn, + project_slug=TEST_PROJECT_NAME, ), } else: blob_item = { - "name": SENTRYCLIRC_FILEPATH, - "rawTextBlob": SENTRYCLIRC_MR_CONTENT.format( - sentry_url=TEST_SENTRY_URL + "name": settings.sentryclirc_filepath, + "rawTextBlob": settings.sentryclirc_mr_content.format( + sentry_url=settings.sentry_url ), } response_dict["node"]["repository"]["blobs"]["nodes"].append(blob_item) @@ -129,7 +121,7 @@ def create_graphql_json_object(**kwargs): if kwargs["sentryclirc_mr_state"]: sentryclirc_mr = { "id": "gid://gitlab/MergeRequest/0001", - "title": SENTRYCLIRC_MR_TITLE.format( + "title": settings.sentryclirc_mr_title.format( project_name=response_dict["node"]["name"] ), "state": kwargs["sentryclirc_mr_state"], @@ -139,7 +131,9 @@ def create_graphql_json_object(**kwargs): if kwargs["dsn_mr_state"]: dsn_mr = { "id": "gid://gitlab/MergeRequest/0001", - "title": DSN_MR_TITLE.format(project_name=response_dict["node"]["name"]), + "title": settings.dsn_mr_title.format( + project_name=response_dict["node"]["name"] + ), "state": kwargs["dsn_mr_state"], } response_dict["node"]["mergeRequests"]["nodes"].append(dsn_mr) @@ -356,3 +350,38 @@ def payload_sentry_project(): sentryclirc_mr_state="merged", dsn_mr_state="merged", ) + + +GRAPHQL_TEST_QUERY = { + "name": "TEST_QUERY", + "instance": "projects", + "body": """ +{ + project(fullPath: "none") { + id + fullPath + name + createdAt + mergeRequestsEnabled + group { + name + } + repository { + blobs { + nodes { + name + rawTextBlob + } + } + } + mergeRequests { + nodes { + id + title + state + } + } + } +} +""", +} diff --git a/tests/resources.py b/tests/resources.py deleted file mode 100644 index a070c34..0000000 --- a/tests/resources.py +++ /dev/null @@ -1,55 +0,0 @@ -TEST_SENTRY_URL = "http://sentry.test.com" -TEST_SENTRY_DSN = "http://test.sentry.test.com" -TEST_SENTRY_TOKEN = "test-token" -TEST_GITLAB_URL = "http://test-gitlab-url" -TEST_GITLAB_TOKEN = "test-token" - -TEST_SENTRY_ENV = "test" -TEST_SENTRY_ORG_SLUG = "test_org" - -# DSN MR configuration. -TEST_DSN_MR_CONTENT = """ -## File generated by gitlab2sentry -[defaults] -url = {sentry_url} -dsn = {dsn} -project = {project_slug} -""" -TEST_DSN_BRANCH_NAME = "auto_add_sentry_dsn" -TEST_DSN_MR_TITLE = ( - "[gitlab2sentry] Merge me to add your sentry DSN to {project_name}" -) -TEST_DSN_MR_DESCRIPTION = """ -{mentions} Congrats, your Sentry project has been -created, merge this -to finalize your Sentry integration of -{name_with_namespace} :clap: :cookie: -""" - -# Sentryclirc MR configuration. -TEST_SENTRYCLIRC_MR_CONTENT = """ -## File generated by gitlab2sentry -[defaults] -url = {sentry_url} -""" -TEST_SENTRYCLIRC_BRANCH_NAME = "auto_add_sentry" -TEST_SENTRYCLIRC_MR_TITLE = ( - "[gitlab2sentry] Merge me to add sentry to {project_name} or close me" -) -TEST_SENTRYCLIRC_FILEPATH = ".sentryclirc" -TEST_SENTRYCLIRC_COM_MSG = "Update .sentryclirc" -TEST_SENTRYCLIRC_MR_DESCRIPTION = """ -{mentions} Merge this and it will automatically -create a Sentry project \n -for {name_with_namespace} :cookie: -""" - -# Gitlab Configuration. -TEST_GITLAB_GRAPHQL_SUFFIX = "test-content" -TEST_GITLAB_GRAPHQL_TIMEOUT = 10 -TEST_GITLAB_GRAPHQL_PAGE_LENGTH = 0 -TEST_GITLAB_AUTHOR_EMAIL = "test-content" -TEST_GITLAB_AUTHOR_NAME = "test-content" -TEST_GITLAB_RMV_SRC_BRANCH = True -TEST_GITLAB_MR_KEYWORD = "sentry" -TEST_GITLAB_MR_LABEL_LIST = ["sentry"] diff --git a/tests/test_gitlab2sentry.py b/tests/test_gitlab2sentry.py index 21b5943..ffbe837 100644 --- a/tests/test_gitlab2sentry.py +++ b/tests/test_gitlab2sentry.py @@ -1,5 +1,5 @@ from gitlab2sentry.exceptions import SentryProjectCreationFailed -from gitlab2sentry.resources import TEST_SENTRY_DSN +from gitlab2sentry.resources import settings from gitlab2sentry.utils import GitlabProvider, SentryProvider from tests.conftest import TEST_GROUP_NAME @@ -218,7 +218,7 @@ def test_create_sentry_project(g2s_fixture, payload_new_project, mocker): payload_new_project["node"]["fullPath"], TEST_GROUP_NAME, payload_new_project["node"]["name"], - payload_new_project["node"]["name"] + payload_new_project["node"]["name"], )["name"] == TEST_GROUP_NAME ) @@ -232,7 +232,7 @@ def test_create_sentry_project(g2s_fixture, payload_new_project, mocker): payload_new_project["node"]["fullPath"], TEST_GROUP_NAME, payload_new_project["node"]["name"], - payload_new_project["node"]["name"] + payload_new_project["node"]["name"], ) mocker.patch.object( @@ -244,7 +244,7 @@ def test_create_sentry_project(g2s_fixture, payload_new_project, mocker): payload_new_project["node"]["fullPath"], TEST_GROUP_NAME, payload_new_project["node"]["name"], - payload_new_project["node"]["name"] + payload_new_project["node"]["name"], ) @@ -285,7 +285,7 @@ def test_handle_g2s_project( mocker.patch.object( g2s_fixture.sentry_provider, attribute="set_rate_limit_for_key", - return_value=TEST_SENTRY_DSN, + return_value=settings.sentry_dsn, ) mocker.patch.object( g2s_fixture, diff --git a/tests/test_gitlab_provider.py b/tests/test_gitlab_provider.py index 277cd78..5fd9833 100644 --- a/tests/test_gitlab_provider.py +++ b/tests/test_gitlab_provider.py @@ -5,19 +5,16 @@ from gql.transport.aiohttp import AIOHTTPTransport from gitlab2sentry.resources import ( - GITLAB_PROJECT_CREATION_LIMIT, GRAPHQL_FETCH_PROJECT_QUERY, GRAPHQL_LIST_PROJECTS_QUERY, - GRAPHQL_TEST_QUERY, - TEST_GITLAB_TOKEN, - TEST_GITLAB_URL, + settings, ) -from tests.conftest import CURRENT_TIME +from tests.conftest import CURRENT_TIME, GRAPHQL_TEST_QUERY def test_get_transport(gql_client_fixture): assert isinstance( - gql_client_fixture._get_transport(TEST_GITLAB_URL, TEST_GITLAB_TOKEN), + gql_client_fixture._get_transport(settings.gitlab_url, settings.gitlab_token), AIOHTTPTransport, ) @@ -67,15 +64,16 @@ def test_project_list_query(gql_client_fixture, payload_new_project, mocker): def test_get_gitlab(gitlab_provider_fixture): assert isinstance( - gitlab_provider_fixture._get_gitlab(TEST_GITLAB_URL, TEST_GITLAB_TOKEN), Gitlab + gitlab_provider_fixture._get_gitlab(settings.gitlab_url, settings.gitlab_token), + Gitlab, ) def test_get_update_limit(gitlab_provider_fixture): - if GITLAB_PROJECT_CREATION_LIMIT: + if settings.gitlab_project_creation_limit: assert ( datetime.now() - gitlab_provider_fixture._get_update_limit() - ).days - GITLAB_PROJECT_CREATION_LIMIT <= 1 + ).days - settings.gitlab_project_creation_limit <= 1 else: assert not gitlab_provider_fixture._get_update_limit() @@ -87,9 +85,9 @@ def test_from_iso_to_datetime(gitlab_provider_fixture): def test_get_default_mentions(gitlab_provider_fixture, gitlab_project_fixture): - _mentioned_members = ( - gitlab_provider_fixture._get_default_mentions(gitlab_project_fixture).split(", ") - ) + _mentioned_members = gitlab_provider_fixture._get_default_mentions( + gitlab_project_fixture + ).split(", ") _project_non_blocked_members = [ member for member in gitlab_project_fixture.members_all.list() diff --git a/tests/test_sentry_provider.py b/tests/test_sentry_provider.py index 2dfc376..1e49f5c 100644 --- a/tests/test_sentry_provider.py +++ b/tests/test_sentry_provider.py @@ -7,7 +7,7 @@ SentryProjectCreationFailed, SentryProjectKeyIDNotFound, ) -from gitlab2sentry.resources import TEST_SENTRY_DSN +from gitlab2sentry.resources import settings from tests.conftest import TEST_GROUP_NAME, TEST_PROJECT_NAME STATUS_CODE, DETAIL = 400, b'{"msg": "error_details"}' @@ -113,7 +113,7 @@ def test_set_rate_limit_for_key(sentry_provider_fixture, mocker): mocker.patch.object( sentry_provider_fixture, attribute="_get_dsn_and_key_id", - return_value=(TEST_SENTRY_DSN, "result"), + return_value=(settings.sentry_dsn, "result"), ) mocker.patch.object( sentry_provider_fixture._client, @@ -122,7 +122,7 @@ def test_set_rate_limit_for_key(sentry_provider_fixture, mocker): ) assert ( sentry_provider_fixture.set_rate_limit_for_key(TEST_PROJECT_NAME) - == TEST_SENTRY_DSN + == settings.sentry_dsn ) mocker.patch.object(