Skip to content

Commit

Permalink
Add base_url to list of django allowed hosts (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
javierdelapuente authored Oct 18, 2024
1 parent 862d84e commit a19233c
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 8 deletions.
18 changes: 17 additions & 1 deletion src/paas_charm/django/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
import pathlib
import secrets
import typing
from urllib.parse import urlsplit

import ops
from pydantic import ConfigDict, Field, validator
from pydantic import BaseModel, ConfigDict, Field, validator

from paas_charm._gunicorn.charm import GunicornBase
from paas_charm.framework import FrameworkConfig
Expand Down Expand Up @@ -75,6 +76,21 @@ def get_cos_dir(self) -> str:
"""
return str((pathlib.Path(__file__).parent / "cos").absolute())

def get_framework_config(self) -> BaseModel:
"""Return the framework related configurations.
The method is overridden to inject the base_url, that can be an ingress URL or a k8s svc
url, to the list of allowed hosts.
Returns:
Framework related configurations.
"""
base_model = super().get_framework_config()
url = urlsplit(self._base_url)
# base_model can be downcasted to a DjangoConfig, and allowed_hosts is really a list.
base_model.allowed_hosts.append(url.hostname) # type: ignore
return base_model

def is_ready(self) -> bool:
"""Check if the charm is ready to start the workload application.
Expand Down
12 changes: 9 additions & 3 deletions tests/integration/django/test_django.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
async def test_django_webserver_timeout(django_app, get_unit_ips, timeout):
"""
arrange: build and deploy the django charm, and change the gunicorn timeout configuration.
act: send long-running requests to the django application managed by the flask charm.
act: send long-running requests to the django application managed by the django charm.
assert: the gunicorn should restart the worker if the request duration exceeds the timeout.
"""
safety_timeout = timeout + 3
Expand All @@ -50,7 +50,9 @@ async def test_django_database_migration(django_app, get_unit_ips):
"update_config, expected_settings",
[
pytest.param(
{"django-allowed-hosts": "*,test"}, {"ALLOWED_HOSTS": ["*", "test"]}, id="allowed-host"
{"django-allowed-hosts": "test"},
{"ALLOWED_HOSTS": ["test", "django-k8s.testing"]},
id="allowed-host",
),
pytest.param({"django-secret-key": "test"}, {"SECRET_KEY": "test"}, id="secret-key"),
],
Expand All @@ -66,7 +68,11 @@ async def test_django_charm_config(django_app, expected_settings, get_unit_ips):
for unit_ip in await get_unit_ips(django_app.name):
for setting, value in expected_settings.items():
url = f"http://{unit_ip}:8000/settings/{setting}"
assert value == requests.get(url, timeout=5).json()
# it is necessary to specify a host header if the IP or '*' is not in ALLOWED_HOSTS
assert (
value
== requests.get(url, headers={"Host": "django-k8s.testing"}, timeout=5).json()
)


async def test_django_create_superuser(django_app, get_unit_ips, run_action):
Expand Down
68 changes: 64 additions & 4 deletions tests/unit/django/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,31 @@
from .constants import DEFAULT_LAYER

TEST_DJANGO_CONFIG_PARAMS = [
pytest.param({}, {"DJANGO_SECRET_KEY": "test", "DJANGO_ALLOWED_HOSTS": "[]"}, id="default"),
pytest.param(
{},
{"DJANGO_SECRET_KEY": "test", "DJANGO_ALLOWED_HOSTS": '["django-k8s.none"]'},
id="default",
),
pytest.param(
{"django-allowed-hosts": "test.local"},
{"DJANGO_SECRET_KEY": "test", "DJANGO_ALLOWED_HOSTS": '["test.local"]'},
{
"DJANGO_SECRET_KEY": "test",
"DJANGO_ALLOWED_HOSTS": '["test.local", "django-k8s.none"]',
},
id="allowed-hosts",
),
pytest.param(
{"django-debug": True},
{"DJANGO_SECRET_KEY": "test", "DJANGO_ALLOWED_HOSTS": "[]", "DJANGO_DEBUG": "true"},
{
"DJANGO_SECRET_KEY": "test",
"DJANGO_ALLOWED_HOSTS": '["django-k8s.none"]',
"DJANGO_DEBUG": "true",
},
id="debug",
),
pytest.param(
{"django-secret-key": "foobar"},
{"DJANGO_SECRET_KEY": "foobar", "DJANGO_ALLOWED_HOSTS": "[]"},
{"DJANGO_SECRET_KEY": "foobar", "DJANGO_ALLOWED_HOSTS": '["django-k8s.none"]'},
id="secret-key",
),
]
Expand Down Expand Up @@ -142,3 +153,52 @@ def test_required_database_integration(harness_no_integrations: Harness):
assert harness.model.unit.status == ops.BlockedStatus(
"Django requires a database integration to work"
)


def test_allowed_hosts_base_hostname_updates_correctly(harness: Harness):
"""
arrange: Deploy a Django charm without an ingress integration
act: Add a new ingress integration
assert: The allowed hosts env var should match the url of the ingress integration
act: Update the url in the ingress integration
assert: The allowed hosts env var should match the new url of the ingress integration
"""
postgresql_relation_data = {
"database": "test-database",
"endpoints": "test-postgresql:5432,test-postgresql-2:5432",
"password": "test-password",
"username": "test-username",
}
harness.add_relation("postgresql", "postgresql-k8s", app_data=postgresql_relation_data)
container = harness.model.unit.get_container("django-app")
container.add_layer("a_layer", DEFAULT_LAYER)
harness.set_model_name("flask-model")
harness.begin_with_initial_hooks()

# The initial allowed hosts matches the k8s service name.
plan = container.get_plan()
env = plan.to_dict()["services"]["django"]["environment"]
assert env["DJANGO_ALLOWED_HOSTS"] == '["django-k8s.flask-model"]'

# Add a relation and the allowed hosts should be updated to the ingress url
harness.add_network("10.0.0.10", endpoint="ingress")
relation_id = harness.add_relation(
"ingress",
"nginx-ingress-integrator",
app_data={"ingress": '{"url": "http://oldjuju.test/"}'},
)

plan = container.get_plan()
env = plan.to_dict()["services"]["django"]["environment"]
assert env["DJANGO_ALLOWED_HOSTS"] == '["oldjuju.test"]'

# Updating the ingress url to a new url should update the allowed hosts.
harness.update_relation_data(
relation_id,
app_or_unit="nginx-ingress-integrator",
key_values={"ingress": '{"url": "http://newjuju.test/"}'},
)

plan = container.get_plan()
env = plan.to_dict()["services"]["django"]["environment"]
assert env["DJANGO_ALLOWED_HOSTS"] == '["newjuju.test"]'

0 comments on commit a19233c

Please sign in to comment.