Skip to content

Commit

Permalink
LITE-28135 add support for django
Browse files Browse the repository at this point in the history
  • Loading branch information
Francesco Faraone committed Jul 20, 2023
1 parent 6d623a0 commit ee42079
Show file tree
Hide file tree
Showing 12 changed files with 888 additions and 150 deletions.
21 changes: 21 additions & 0 deletions connect/eaas/runner/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ def instance_id(self):
def environment_type(self):
return self.dyn_config.environment_type

@property
def environment_runtime(self):
return self.dyn_config.environment_runtime

@property
def environment_hostname(self):
return self.dyn_config.hostname

@property
def environment_domain(self):
return self.dyn_config.domain

@property
def account_id(self):
return self.dyn_config.logging.meta.account_id
Expand Down Expand Up @@ -176,6 +188,15 @@ def update_dynamic_config(self, data):
self.dyn_config.environment_type = (
data.environment_type or self.dyn_config.environment_type
)
self.dyn_config.environment_runtime = (
data.environment_runtime or self.dyn_config.environment_runtime
)
self.dyn_config.environment_hostname = (
data.environment_hostname or self.dyn_config.environment_hostname
)
self.dyn_config.environment_domain = (
data.environment_domain or self.dyn_config.environment_domain
)
self.dyn_config.logging.meta.account_id = (
data.logging.meta.account_id or self.dyn_config.logging.meta.account_id
)
Expand Down
28 changes: 28 additions & 0 deletions connect/eaas/runner/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,31 @@

EXCEL_NULL_MARKER = '#N/A'
ROW_DELETED_MARKER = '#INSTRUCTION/DELETE_ROW'

DJANGO_NON_OVERRIDEABLE_SETTINGS = (
'FORCE_SCRIPT_NAME',
'INSTALLED_APPS',
'ROOT_URLCONF',
'CSRF_COOKIE_SECURE',
'SECURE_CONTENT_TYPE_NOSNIFF',
'SECURE_CROSS_ORIGIN_OPENER_POLICY',
'SECURE_HSTS_INCLUDE_SUBDOMAINS',
'SECURE_HSTS_PRELOAD',
'SECURE_HSTS_SECONDS',
'SECURE_PROXY_SSL_HEADER',
'USE_X_FORWARDED_HOST',
'USE_X_FORWARDED_PORT',
'WSGI_APPLICATION',
'SESSION_COOKIE_HTTPONLY',
'SESSION_COOKIE_PATH',
'SESSION_COOKIE_SAMESITE',
'SESSION_COOKIE_SECURE',
)
DJANGO_REQUIRED_OVERRIDE_SETTINGS = ('SECRET_KEY',)
DJANGO_ENFORCED_SETTINGS = {
'CSRF_COOKIE_SECURE': True,
'SECURE_PROXY_SSL_HEADER': ("HTTP_X_FORWARDED_PROTO", "https"),
'USE_X_FORWARDED_HOST': True,
'USE_X_FORWARDED_PORT': True,
'SESSION_COOKIE_SECURE': True,
}
22 changes: 22 additions & 0 deletions connect/eaas/runner/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ApplicationHandlerBase(ABC):
def __init__(self, config):
self._config = config
self._logging_handler = None
self._django_settings_module = self.get_django_settings_module()

@property
def config(self):
Expand Down Expand Up @@ -70,7 +71,28 @@ def features(self):
def variables(self):
return self.get_variables()

@property
def django_settings_module(self):
return self._django_settings_module

def get_django_settings_module(self):
ep = next(
iter_entry_points('connect.eaas.ext', 'djsettings'),
None,
)
if ep:
get_settings = ep.load()
return get_settings()
return None

def load_django(self):
if self._django_settings_module:
os.environ.setdefault('DJANGO_SETTINGS_MODULE', self._django_settings_module)
import django
django.setup()

def load_application(self, name):
self.load_django()
ep = next(
iter_entry_points('connect.eaas.ext', name),
None,
Expand Down
33 changes: 33 additions & 0 deletions connect/eaas/runner/handlers/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import functools
import inspect
import logging
import os

from fastapi import (
FastAPI,
Expand Down Expand Up @@ -186,9 +187,41 @@ def get_asgi_application(self):
static_root = self.get_application().get_static_root()
if static_root:
app.mount('/static', StaticFiles(directory=static_root), name='static')
if self.django_settings_module:
self.mount_django_apps(app)

return app

def mount_django_apps(self, app):
from django.conf import settings # noqa: I001
from django.core.exceptions import ImproperlyConfigured # noqa: I001
from django.core.handlers.asgi import ASGIHandler # noqa: I001
script_name = getattr(settings, 'FORCE_SCRIPT_NAME', None)
if not (
script_name
and script_name.startswith('/guest/')
and len(script_name) > len('/guest/')
):
raise ImproperlyConfigured(
'`FORCE_SCRIPT_NAME` must be set and must start with "/guest/"',
)
static_root = self.get_application().get_static_root()
if not (
settings.STATIC_ROOT.startswith(static_root)
and len(settings.STATIC_ROOT) > len(static_root) - 1
):
raise ImproperlyConfigured(
f'`STATIC_ROOT` must be a directory within {static_root}',
)
static_path = os.path.join(script_name, settings.STATIC_URL)
app.mount(
static_path,
StaticFiles(
directory=settings.STATIC_ROOT,
),
)
app.mount(script_name, ASGIHandler())

def setup_middlewares(self, app, middlewares):
for middleware in middlewares:
if inspect.isfunction(middleware):
Expand Down
44 changes: 44 additions & 0 deletions connect/eaas/runner/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#
# Copyright (c) 2022 Ingram Micro. All Rights Reserved.
#
import copy
import logging
import os
import subprocess
Expand Down Expand Up @@ -47,6 +48,9 @@
)
from connect.eaas.runner.constants import (
BACKGROUND_TASK_MAX_EXECUTION_TIME,
DJANGO_ENFORCED_SETTINGS,
DJANGO_NON_OVERRIDEABLE_SETTINGS,
DJANGO_REQUIRED_OVERRIDE_SETTINGS,
HANDLER_CLASS_TITLE,
INTERACTIVE_TASK_MAX_EXECUTION_TIME,
ORDINAL_SUFFIX,
Expand Down Expand Up @@ -618,3 +622,43 @@ def get_features_table(features):
Align.center(get_tfnapp_detail_table(details)),
)
return table


def enforce_and_override_django_settings(config, overridden_settings):
from django.conf import settings # noqa: I001
from django.core.exceptions import ImproperlyConfigured # noqa: I001
overrides = copy.copy(overridden_settings)
required_overrides = list(DJANGO_REQUIRED_OVERRIDE_SETTINGS)
if config.environment_runtime == 'cloud':
required_overrides.append('DATABASES')
for required_setting in required_overrides:
if required_setting not in overrides.keys():
raise ImproperlyConfigured(
f'The settings `{",".join(required_overrides)}` must be overridden.',
)
for non_overrideable_setting in DJANGO_NON_OVERRIDEABLE_SETTINGS:
if non_overrideable_setting in overrides.keys():
raise ImproperlyConfigured(
'The settings '
f'`{",".join(DJANGO_NON_OVERRIDEABLE_SETTINGS)}` cannot be overridden.',
)
databases_override = overrides.pop('DATABASES', None)

for setting, setting_value in overrides.items():
setattr(settings, setting, setting_value)

if databases_override:
for db, db_config in databases_override.items():
for prop, propvalue in db_config.items():
settings.DATABASES[db][prop] = propvalue

for setting, setting_value in DJANGO_ENFORCED_SETTINGS.items():
setattr(settings, setting, setting_value)

if config.environment_hostname and config.environment_domain:
settings.ALLOWED_HOSTS = [
f'{config.environment_hostname}.{config.environment_domain}',
]

if config.environment_runtime == 'cloud':
settings.DEBUG = False
25 changes: 17 additions & 8 deletions connect/eaas/runner/workers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
StopBackoffError,
)
from connect.eaas.runner.helpers import (
enforce_and_override_django_settings,
to_ordinal,
)

Expand Down Expand Up @@ -221,6 +222,11 @@ async def process_setup_response(self, data):
reconfigured, then restart the tasks manager.
"""
self.config.update_dynamic_config(data)
if self.handler.django_settings_module:
enforce_and_override_django_settings(
self.config,
await self.invoke_hook('get_django_settings'),
)
await self.trigger_event('on_startup')
logger.info('Extension configuration has been updated.')

Expand All @@ -235,19 +241,22 @@ async def trigger_event(self, event):
return
self.on_shutdown_fired.value = 1

await self.invoke_hook(event)

async def invoke_hook(self, hook_name):
application = self.handler.get_application()
event_handler = getattr(application, event, None)
hook = getattr(application, hook_name, None)
if (
event_handler
and inspect.ismethod(event_handler)
and event_handler.__self__ is application
hook
and inspect.ismethod(hook)
and hook.__self__ is application
):
if inspect.iscoroutinefunction(event_handler):
await event_handler(self.handler.get_logger(), self.config.variables)
if inspect.iscoroutinefunction(hook):
return await hook(self.handler.get_logger(), self.config.variables)
else:
await asyncio.get_event_loop().run_in_executor(
return await asyncio.get_event_loop().run_in_executor(
None,
event_handler,
hook,
self.handler.get_logger(),
self.config.variables,
)
Expand Down
Loading

0 comments on commit ee42079

Please sign in to comment.