From ed647b2d4a593b0ae955758c43765d20dc12fa45 Mon Sep 17 00:00:00 2001 From: Zeid Zabaneh Date: Fri, 22 Mar 2024 11:38:56 -0400 Subject: [PATCH] api, main: port various functionality (bug 1887013) WIP --- src/lando/api/legacy/api/diff_warnings.py | 2 +- src/lando/api/legacy/api/landing_jobs.py | 3 +- src/lando/api/legacy/api/stacks.py | 2 +- src/lando/api/legacy/api/transplants.py | 3 +- src/lando/api/legacy/api/try_push.py | 8 ++---- src/lando/api/legacy/api/uplift.py | 2 +- src/lando/api/legacy/auth.py | 8 +++--- src/lando/api/legacy/cache.py | 4 +-- src/lando/api/legacy/celery.py | 4 +-- src/lando/api/legacy/decorators.py | 2 +- src/lando/api/legacy/hooks.py | 2 +- src/lando/api/legacy/phabricator.py | 9 +++--- src/lando/api/legacy/repos.py | 8 +++--- src/lando/api/legacy/sentry.py | 8 +++--- src/lando/api/legacy/smtp.py | 26 ++++++++--------- src/lando/api/legacy/transplants.py | 2 +- src/lando/api/legacy/treestatus.py | 4 +-- src/lando/api/legacy/ui.py | 2 +- src/lando/api/legacy/validation.py | 2 +- src/lando/api/tests/test_auth.py | 5 ++-- src/lando/api/tests/test_decorators.py | 2 +- src/lando/api/tests/test_validation.py | 2 +- src/lando/main/support.py | 35 +++++++++++++++++++++++ 23 files changed, 86 insertions(+), 59 deletions(-) create mode 100644 src/lando/main/support.py diff --git a/src/lando/api/legacy/api/diff_warnings.py b/src/lando/api/legacy/api/diff_warnings.py index 8109055b..e0c03fb1 100644 --- a/src/lando/api/legacy/api/diff_warnings.py +++ b/src/lando/api/legacy/api/diff_warnings.py @@ -11,7 +11,7 @@ import logging -from connexion import problem +from lando.main.support import problem from lando.api.legacy.decorators import require_phabricator_api_key from lando.main.models.revision import DiffWarning, DiffWarningStatus diff --git a/src/lando/api/legacy/api/landing_jobs.py b/src/lando/api/legacy/api/landing_jobs.py index ccdaeeb2..562389d8 100644 --- a/src/lando/api/legacy/api/landing_jobs.py +++ b/src/lando/api/legacy/api/landing_jobs.py @@ -3,8 +3,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. import logging -from connexion import ProblemException -from flask import g +from lando.main.support import ProblemException, g from lando.api import auth from lando.main.models.landing_job import LandingJob, LandingJobAction, LandingJobStatus diff --git a/src/lando/api/legacy/api/stacks.py b/src/lando/api/legacy/api/stacks.py index 20d1db1d..6ab797dc 100644 --- a/src/lando/api/legacy/api/stacks.py +++ b/src/lando/api/legacy/api/stacks.py @@ -4,7 +4,7 @@ import logging import urllib.parse -from connexion import problem +from lando.main.support import problem from lando import settings from lando.api.legacy.commit_message import format_commit_message diff --git a/src/lando/api/legacy/api/transplants.py b/src/lando/api/legacy/api/transplants.py index 7e3620e4..d81b5833 100644 --- a/src/lando/api/legacy/api/transplants.py +++ b/src/lando/api/legacy/api/transplants.py @@ -7,9 +7,8 @@ from typing import Optional import kombu -from connexion import ProblemException, problem +from lando.main.support import ProblemException, problem, g from lando import settings -from flask import g from lando.api import auth from lando.api.legacy.commit_message import format_commit_message diff --git a/src/lando/api/legacy/api/try_push.py b/src/lando/api/legacy/api/try_push.py index 8dbe3e00..f3bacb4c 100644 --- a/src/lando/api/legacy/api/try_push.py +++ b/src/lando/api/legacy/api/try_push.py @@ -8,11 +8,7 @@ import io import logging -from connexion import ProblemException -from flask import ( - current_app, - g, -) +from lando.main.support import ProblemException, g from lando.api import auth from lando.api.legacy.hgexports import ( @@ -110,7 +106,7 @@ def post_patches(data: dict): patches = data["patches"] patch_format = PatchFormat(data["patch_format"]) - environment_repos = get_repos_for_env(current_app.config.get("ENVIRONMENT")) + environment_repos = get_repos_for_env(settings.ENVIRONMENT) try_repo = environment_repos.get("try") if not try_repo: raise ProblemException( diff --git a/src/lando/api/legacy/api/uplift.py b/src/lando/api/legacy/api/uplift.py index 289726e6..a59781fe 100644 --- a/src/lando/api/legacy/api/uplift.py +++ b/src/lando/api/legacy/api/uplift.py @@ -4,7 +4,7 @@ import logging -from connexion import problem +from lando.main.support import problem from lando import settings from lando.api import auth diff --git a/src/lando/api/legacy/auth.py b/src/lando/api/legacy/auth.py index c300ec1f..71953797 100644 --- a/src/lando/api/legacy/auth.py +++ b/src/lando/api/legacy/auth.py @@ -15,9 +15,9 @@ ) import requests -from connexion import ProblemException, request +from lando.main.support import ProblemException, request from lando import settings -from flask import g +from lando.main.support import g from jose import jwt from django.core.cache import cache @@ -535,8 +535,8 @@ class Auth0Subsystem(Subsystem): name = "auth0" def ready(self) -> bool | str: - domain = self.flask_app.config.get("OIDC_DOMAIN") - identifier = self.flask_app.config.get("OIDC_IDENTIFIER") + domain = settings.OIDC_DOMAIN + identifier = settings.OIDC_IDENTIFIER # OIDC_DOMAIN should be the domain assigned to the auth0 organization. # Leaving this unset could cause an application security problem. We diff --git a/src/lando/api/legacy/cache.py b/src/lando/api/legacy/cache.py index 4569081c..b1117389 100644 --- a/src/lando/api/legacy/cache.py +++ b/src/lando/api/legacy/cache.py @@ -26,8 +26,8 @@ class CacheSubsystem(Subsystem): def init_app(self, app): super().init_app(app) - host = self.flask_app.config.get("CACHE_REDIS_HOST") - if self.flask_app.config.get("CACHE_DISABLED"): + host = settings.CACHE_REDIS_HOST + if settings.CACHE_DISABLED: # Default to not caching for testing. logger.warning("Cache initialized in null mode.") cache_config = {"CACHE_TYPE": "NullCache"} diff --git a/src/lando/api/legacy/celery.py b/src/lando/api/legacy/celery.py index 5121a1aa..996eb3bc 100644 --- a/src/lando/api/legacy/celery.py +++ b/src/lando/api/legacy/celery.py @@ -142,12 +142,12 @@ def init_app(self, app): celery.init_app( self.flask_app, - config={"broker_url": self.flask_app.config.get("CELERY_BROKER_URL")}, + config={"broker_url": settings.CELERY_BROKER_URL}, ) celery.log.setup() def ready(self): - if self.flask_app.config.get("DISABLE_CELERY"): + if settings.DISABLE_CELERY: return True # TODO: Check connection to CELERY_BROKER_URL diff --git a/src/lando/api/legacy/decorators.py b/src/lando/api/legacy/decorators.py index 74284131..ec9199ce 100644 --- a/src/lando/api/legacy/decorators.py +++ b/src/lando/api/legacy/decorators.py @@ -6,7 +6,7 @@ Callable, ) -from connexion import problem, request +from lando.main.support import problem, request from lando import settings from lando.api.legacy.phabricator import PhabricatorClient diff --git a/src/lando/api/legacy/hooks.py b/src/lando/api/legacy/hooks.py index c15d3c8f..0b437905 100644 --- a/src/lando/api/legacy/hooks.py +++ b/src/lando/api/legacy/hooks.py @@ -6,7 +6,7 @@ import time from typing import Optional -from connexion import FlaskApi, problem +from lando.main.support import FlaskApi, problem from flask import ( Flask, Response, diff --git a/src/lando/api/legacy/phabricator.py b/src/lando/api/legacy/phabricator.py index 0ca634c0..3657073a 100644 --- a/src/lando/api/legacy/phabricator.py +++ b/src/lando/api/legacy/phabricator.py @@ -24,6 +24,7 @@ import requests +from lando import settings from lando.api.legacy.systems import Subsystem logger = logging.getLogger(__name__) @@ -362,8 +363,8 @@ class PhabricatorSubsystem(Subsystem): name = "phabricator" def ready(self) -> bool | str: - unpriv_key = self.flask_app.config["PHABRICATOR_UNPRIVILEGED_API_KEY"] - priv_key = self.flask_app.config["PHABRICATOR_ADMIN_API_KEY"] + unpriv_key = settings.PHABRICATOR_UNPRIVILEGED_API_KEY + priv_key = settings.PHABRICATOR_ADMIN_API_KEY if unpriv_key and PHAB_API_KEY_RE.search(unpriv_key) is None: return ( @@ -382,8 +383,8 @@ def ready(self) -> bool | str: def healthy(self) -> bool | str: try: PhabricatorClient( - self.flask_app.config["PHABRICATOR_URL"], - self.flask_app.config["PHABRICATOR_UNPRIVILEGED_API_KEY"], + settings.PHABRICATOR_URL, + settings.PHABRICATOR_UNPRIVILEGED_API_KEY, ).call_conduit("conduit.ping") except PhabricatorAPIException as exc: return "PhabricatorAPIException: {!s}".format(exc) diff --git a/src/lando/api/legacy/repos.py b/src/lando/api/legacy/repos.py index 7dd77ffb..c0cf75e4 100644 --- a/src/lando/api/legacy/repos.py +++ b/src/lando/api/legacy/repos.py @@ -408,13 +408,13 @@ class RepoCloneSubsystem(Subsystem): name = "repo_clone" def ready(self) -> Optional[bool | str]: - clones_path = self.flask_app.config["REPO_CLONES_PATH"] - repo_names = self.flask_app.config["REPOS_TO_LAND"] + clones_path = settings.REPO_CLONES_PATH + repo_names = settings.REPOS_TO_LAND if not clones_path and not repo_names: return None - clones_path = pathlib.Path(self.flask_app.config["REPO_CLONES_PATH"]) + clones_path = pathlib.Path(settings.REPO_CLONES_PATH) if not clones_path.exists() or not clones_path.is_dir(): return ( "REPO_CLONES_PATH ({}) is not a valid path to an existing " @@ -428,7 +428,7 @@ def ready(self) -> Optional[bool | str]: "of repository names." ) - repos = get_repos_for_env(self.flask_app.config.get("ENVIRONMENT")) + repos = get_repos_for_env(settings.ENVIRONMENT) if not all(name in repos for name in repo_names): return "REPOS_TO_LAND contains unsupported repository names." diff --git a/src/lando/api/legacy/sentry.py b/src/lando/api/legacy/sentry.py index ddf4583a..36aaadd2 100644 --- a/src/lando/api/legacy/sentry.py +++ b/src/lando/api/legacy/sentry.py @@ -4,7 +4,7 @@ import logging import sentry_sdk -from sentry_sdk.integrations.flask import FlaskIntegration +from sentry_sdk.integrations.django import DjangoIntegration from lando.api.legacy.systems import Subsystem @@ -30,14 +30,14 @@ class SentrySubsystem(Subsystem): def init_app(self, app): super().init_app(app) - sentry_dsn = self.flask_app.config.get("SENTRY_DSN") + sentry_dsn = settings.SENTRY_DSN logger.info("sentry status", extra={"enabled": bool(sentry_dsn)}) sentry_sdk.init( before_send=before_send, dsn=sentry_dsn, - integrations=[FlaskIntegration()], + integrations=[DjangoIntegration()], traces_sample_rate=1.0, - release=self.flask_app.config.get("VERSION").get("version", "0.0.0"), + release=self.settings.VERSION.get("version", "0.0.0"), ) diff --git a/src/lando/api/legacy/smtp.py b/src/lando/api/legacy/smtp.py index d7a68c21..d014b0d8 100644 --- a/src/lando/api/legacy/smtp.py +++ b/src/lando/api/legacy/smtp.py @@ -23,26 +23,26 @@ def init_app(self, app): def suppressed(self): return ( self.flask_app is None - or bool(self.flask_app.config.get("MAIL_SUPPRESS_SEND")) - or not self.flask_app.config.get("MAIL_SERVER") + or bool(settings.MAIL_SUPPRESS_SEND) + or not settings.MAIL_SERVER ) @property def default_from(self): - return self.flask_app.config.get("MAIL_FROM") or "mozphab-prod@mozilla.com" + return settings.MAIL_FROM or "mozphab-prod@mozilla.com" @contextmanager def connection(self): if self.suppressed: raise ValueError("Supressed SMTP has no connection") - host = self.flask_app.config.get("MAIL_SERVER") or None - port = self.flask_app.config.get("MAIL_PORT") or None - use_ssl = self.flask_app.config.get("MAIL_USE_SSL") - use_tls = self.flask_app.config.get("MAIL_USE_TLS") + host = settings.MAIL_SERVER or None + port = settings.MAIL_PORT or None + use_ssl = settings.MAIL_USE_SSL + use_tls = settings.MAIL_USE_TLS - username = self.flask_app.config.get("MAIL_USERNAME") or None - password = self.flask_app.config.get("MAIL_PASSWORD") or None + username = settings.MAIL_USERNAME or None + password = settings.MAIL_PASSWORD or None smtp_class = smtplib.SMTP_SSL if use_ssl else smtplib.SMTP c = smtp_class(host, port) @@ -60,7 +60,7 @@ def recipient_allowed(self, email): if self.flask_app is None: return True - whitelist = self.flask_app.config.get("MAIL_RECIPIENT_WHITELIST") or None + whitelist = settings.MAIL_RECIPIENT_WHITELIST or None if whitelist is None: return True @@ -82,10 +82,8 @@ def ready(self): logger.warning( "SMTP is suppressed, assuming ready", extra={ - "MAIL_SERVER": self.flask_app.config.get("MAIL_SERVER"), - "MAIL_SUPPRESS_SEND": self.flask_app.config.get( - "MAIL_SUPPRESS_SEND" - ), + "MAIL_SERVER": settings.MAIL_SERVER, + "MAIL_SUPPRESS_SEND": settings.MAIL_SUPPRESS_SEND, }, ) return True diff --git a/src/lando/api/legacy/transplants.py b/src/lando/api/legacy/transplants.py index 5dd740f3..a249b774 100644 --- a/src/lando/api/legacy/transplants.py +++ b/src/lando/api/legacy/transplants.py @@ -9,7 +9,7 @@ from datetime import datetime, timezone import requests -from connexion import ProblemException +from lando.main.support import ProblemException from lando import settings from lando.main.models.landing_job import LandingJob, LandingJobStatus diff --git a/src/lando/api/legacy/treestatus.py b/src/lando/api/legacy/treestatus.py index f6f65601..6934c79c 100644 --- a/src/lando/api/legacy/treestatus.py +++ b/src/lando/api/legacy/treestatus.py @@ -156,8 +156,8 @@ class TreeStatusSubsystem(Subsystem): def init_app(self, app): super().init_app(app) - self.client = TreeStatus(url=self.flask_app.config["TREESTATUS_URL"]) - version_sha = self.flask_app.config["VERSION"].get("version", "dev") + self.client = TreeStatus(url=settings.TREESTATUS_URL) + version_sha = settings.VERSION.get("version", "dev") self.client.session.headers.update( {"User-Agent": f"landoapi.treestatus.TreeStatus/{version_sha}"} ) diff --git a/src/lando/api/legacy/ui.py b/src/lando/api/legacy/ui.py index 1744e840..fdadaf0e 100644 --- a/src/lando/api/legacy/ui.py +++ b/src/lando/api/legacy/ui.py @@ -16,7 +16,7 @@ class LandoUISubsystem(Subsystem): name = "lando_ui" def ready(self) -> bool | str: - url = urlparse(self.flask_app.config["LANDO_UI_URL"]) + url = urlparse(settings.LANDO_UI_URL) if not url.scheme or not url.netloc: return "Invalid LANDO_UI_URL, missing a scheme and/or hostname" diff --git a/src/lando/api/legacy/validation.py b/src/lando/api/legacy/validation.py index 677671a4..bde1c0fa 100644 --- a/src/lando/api/legacy/validation.py +++ b/src/lando/api/legacy/validation.py @@ -3,7 +3,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. import re -from connexion import ProblemException +from lando.main.support import ProblemException REVISION_ID_RE = re.compile(r"^D(?P[1-9][0-9]*)$") diff --git a/src/lando/api/tests/test_auth.py b/src/lando/api/tests/test_auth.py index c27ce925..5ced4a20 100644 --- a/src/lando/api/tests/test_auth.py +++ b/src/lando/api/tests/test_auth.py @@ -7,9 +7,8 @@ import pytest import requests import requests_mock -from connexion import ProblemException -from connexion.lifecycle import ConnexionResponse -from flask import g +from lando.main.support import ProblemException, ConnexionResponse +from lando.main.support import g from lando.api.legacy.auth import ( A0User, diff --git a/src/lando/api/tests/test_decorators.py b/src/lando/api/tests/test_decorators.py index 39ab2202..4491102b 100644 --- a/src/lando/api/tests/test_decorators.py +++ b/src/lando/api/tests/test_decorators.py @@ -2,7 +2,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import pytest -from connexion.lifecycle import ConnexionResponse +from lando.main.support import ConnexionResponse from lando.api.legacy.decorators import require_phabricator_api_key from lando.api.legacy.phabricator import PhabricatorClient diff --git a/src/lando/api/tests/test_validation.py b/src/lando/api/tests/test_validation.py index 44f2543c..ab58e4b1 100644 --- a/src/lando/api/tests/test_validation.py +++ b/src/lando/api/tests/test_validation.py @@ -2,7 +2,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import pytest -from connexion import ProblemException +from lando.main.support import ProblemException from lando.api.legacy.validation import revision_id_to_int diff --git a/src/lando/main/support.py b/src/lando/main/support.py new file mode 100644 index 00000000..414a1968 --- /dev/null +++ b/src/lando/main/support.py @@ -0,0 +1,35 @@ +from django.http import HttpResponse + + +class ProblemException(Exception): + def __init__(self, *, status=500, title=None, detail=None, type=None, instance=None, headers=None, ext=None): + # TODO: this should be reimplemented as either middleware or HttpResponse return values. + super().__init__(self) + + +def problem(status, title, detail, type=None, instance=None, headers=None, ext=None): + return HttpResponse(content=detail, headers=headers, status_code=status) + + +request = { + "headers": {}, +} + +session = {} + + +class g: + auth0_user = None + access_token = None + access_token_payload = None + _request_start_timestamp = None + + +class FlaskApi: + @classmethod + def get_response(self, _problem): + return _problem + + +class ConnexionResponse(HttpResponse): + pass \ No newline at end of file