Skip to content

Commit cbe47eb

Browse files
committed
feat(domain): add procfile_type
1 parent 153c60d commit cbe47eb

12 files changed

Lines changed: 445 additions & 122 deletions
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.2.10 on 2024-05-01 13:29
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api', '0006_token'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='domain',
15+
name='procfile_type',
16+
field=models.TextField(default='web'),
17+
),
18+
]

rootfs/api/models/domain.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Domain(AuditedModel):
2121
blank=True,
2222
null=True
2323
)
24+
procfile_type = models.TextField()
2425

2526
class Meta:
2627
ordering = ['domain', 'certificate']

rootfs/api/models/gateway.py

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
DEFAULT_HTTP_PORT = 80
1616
DEFAULT_HTTPS_PORT = 443
1717

18+
HOSTNAME_PROTOCOLS = ("TLS", "HTTP", "HTTPS")
19+
1820

1921
class Gateway(AuditedModel):
2022
app = models.ForeignKey('App', on_delete=models.CASCADE)
@@ -46,27 +48,27 @@ def listeners(self):
4648
domains = list(self._get_tls_domain(auto_tls))
4749
for item in self.ports:
4850
port, protocol = item["port"], item["protocol"]
49-
if item["protocol"] in ("TLS", "HTTPS"):
51+
if item["protocol"] in HOSTNAME_PROTOCOLS:
5052
for domain in domains:
51-
secret_name = f"{self.app.id}-auto-tls" if auto_tls else (
52-
domain.certificate.name if domain.certificate else None)
53-
if secret_name is None:
54-
continue
55-
listeners.append({
53+
listener = {
5654
"allowedRoutes": {"namespaces": {"from": "All"}},
5755
"name": self._get_listener_name(port, protocol, domains.index(domain)),
5856
"port": port,
5957
"hostname": domain.domain,
6058
"protocol": protocol,
61-
"tls": {"certificateRefs": [{"kind": "Secret", "name": secret_name}]},
62-
})
63-
else:
64-
listeners.append({
65-
"allowedRoutes": {"namespaces": {"from": "All"}},
66-
"name": self._get_listener_name(port, protocol, 0),
67-
"port": port,
68-
"protocol": protocol,
69-
})
59+
}
60+
secret_name = f"{self.app.id}-auto-tls" if auto_tls else (
61+
domain.certificate.name if domain.certificate else None)
62+
if secret_name:
63+
listener["tls"] = {
64+
"certificateRefs": [{"kind": "Secret", "name": secret_name}]}
65+
listeners.append(listener)
66+
listeners.append({
67+
"allowedRoutes": {"namespaces": {"from": "All"}},
68+
"name": self._get_listener_name(port, protocol, 0),
69+
"port": port,
70+
"protocol": protocol,
71+
})
7072
return listeners
7173

7274
@property
@@ -183,6 +185,11 @@ def protocols(self):
183185
raise NotImplementedError("this kind is not supported")
184186
return self.PROTOCOLS_CHOICES[self.kind]
185187

188+
@property
189+
def hostnames(self):
190+
return [domain.domain for domain in self.app.domain_set.filter(
191+
procfile_type=self.procfile_type)]
192+
186193
@property
187194
def default_rules(self):
188195
service = get_object_or_404(self.app.service_set, procfile_type=self.procfile_type)
@@ -259,7 +266,7 @@ def change_default_tls(self):
259266
def attach(self, gateway_name, port):
260267
ok, msg = self._check_parent(gateway_name, port)
261268
if not ok:
262-
return ok, msg
269+
return ok, {"detail": msg}
263270
parent_ref = {"name": gateway_name, "port": port}
264271
if parent_ref in self.parent_refs:
265272
return False, {"detail": "gateway and port already exist in this route"}
@@ -302,38 +309,38 @@ def _check_parent(self, gateway_name, port):
302309
try:
303310
gateway = self.app.gateway_set.filter(name=gateway_name).latest()
304311
except Gateway.DoesNotExist:
305-
return False, {"detail": f"this gateway {gateway_name} does not exist"}
312+
return False, f"this gateway {gateway_name} does not exist"
306313
is_listener_allowed = False
307314
for gateway_port in gateway.ports:
308315
if port == gateway_port.get("port") and \
309316
self.kind.split("Route")[0] in gateway_port.get("protocol"):
310317
is_listener_allowed = True
311318
if not is_listener_allowed:
312-
return False, {"detail": f"this gateway does not allow {self.kind} port {port} bind, \nplease add gateway listener first."} # noqa
319+
return False, "listener does not exist, please add gateway listener first."
313320
for route in self.app.route_set.exclude(app=self.app, name=self.name):
314321
for parent_ref in route.parent_refs:
315322
if parent_ref["name"] == gateway_name and parent_ref["port"] == port:
316-
for protocol in self.protocols:
317-
if protocol in route.protocols:
318-
return False, {"detail": "this listener has already been referenced"}
323+
if not set(route.protocols).issubset(HOSTNAME_PROTOCOLS) and (
324+
set(route.protocols).issubset(self.protocols) or
325+
set(self.protocols).issubset(route.protocols)):
326+
return False, "this listener has already been referenced"
319327
return True, ""
320328

321329
def _refresh_to_k8s(self, rules, parent_refs):
322330
try:
323331
k8s_route = getattr(self.scheduler(), self.kind.lower())
324-
hostnames = [domain.domain for domain in self.app.domain_set.all()]
325332
try:
326333
data = k8s_route.get(self.app.id, self.name).json()
327334
k8s_route.patch(self.app.id, self.name, **{
328335
"rules": rules,
329-
"hostnames": hostnames,
336+
"hostnames": self.hostnames,
330337
"parent_refs": parent_refs,
331338
"version": data["metadata"]["resourceVersion"],
332339
})
333340
except KubeException:
334341
k8s_route.create(self.app.id, self.name, **{
335342
"rules": rules,
336-
"hostnames": hostnames,
343+
"hostnames": self.hostnames,
337344
"parent_refs": parent_refs,
338345
})
339346
except KubeException as e:

rootfs/api/serializers/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ class DomainSerializer(serializers.ModelSerializer):
410410
class Meta:
411411
"""Metadata options for a :class:`DomainSerializer`."""
412412
model = models.domain.Domain
413-
fields = ['owner', 'created', 'updated', 'app', 'domain']
413+
fields = ['owner', 'created', 'updated', 'app', 'domain', 'procfile_type']
414414
read_only_fields = ['uuid']
415415

416416
@staticmethod
@@ -460,6 +460,13 @@ def ToACE(x): return idna.alabel(x).decode("utf-8", "strict")
460460

461461
return aceValue
462462

463+
@staticmethod
464+
def validate_procfile_type(value):
465+
if not re.match(PROCTYPE_MATCH, value):
466+
raise serializers.ValidationError(PROCTYPE_MISMATCH_MSG)
467+
468+
return value
469+
463470

464471
class ServiceSerializer(serializers.ModelSerializer):
465472
"""Serialize a :class:`~api.models.service.Service` model."""

rootfs/api/tests/test_certificate_use_case_1.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.contrib.auth import get_user_model
22
from django.core.cache import cache
33

4-
from api.models.app import App
4+
from api.models.app import App, PROCFILE_TYPE_WEB
55
from api.models.certificate import Certificate
66
from api.models.domain import Domain
77
from api.tests import TEST_ROOT, DryccTestCase
@@ -25,7 +25,8 @@ def setUp(self):
2525

2626
self.url = '/v2/certs'
2727
self.app = App.objects.create(owner=self.user, id='test-app-use-case-1')
28-
self.domain = Domain.objects.create(owner=self.user, app=self.app, domain='foo.com')
28+
self.domain = Domain.objects.create(
29+
owner=self.user, app=self.app, domain='foo.com', procfile_type=PROCFILE_TYPE_WEB)
2930
self.name = 'foo-com' # certificate name
3031

3132
with open('{}/certs/{}.key'.format(TEST_ROOT, self.domain)) as f:

rootfs/api/tests/test_certificate_use_case_2.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.contrib.auth import get_user_model
22
from django.core.cache import cache
33

4-
from api.models.app import App
4+
from api.models.app import App, PROCFILE_TYPE_WEB
55
from api.models.certificate import Certificate
66
from api.models.domain import Domain
77
from api.tests import TEST_ROOT, DryccTestCase
@@ -26,8 +26,10 @@ def setUp(self):
2626
self.url = '/v2/certs'
2727
self.app = App.objects.create(owner=self.user, id='test-app-use-case-2')
2828
self.domains = {
29-
'foo.com': Domain.objects.create(owner=self.user, app=self.app, domain='foo.com'),
30-
'bar.com': Domain.objects.create(owner=self.user, app=self.app, domain='bar.com'),
29+
'foo.com': Domain.objects.create(
30+
owner=self.user, app=self.app, domain='foo.com', procfile_type=PROCFILE_TYPE_WEB),
31+
'bar.com': Domain.objects.create(
32+
owner=self.user, app=self.app, domain='bar.com', procfile_type=PROCFILE_TYPE_WEB),
3133
}
3234

3335
# only foo.com has a cert

rootfs/api/tests/test_certificate_use_case_3.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.contrib.auth import get_user_model
22
from django.core.cache import cache
33

4-
from api.models.app import App
4+
from api.models.app import App, PROCFILE_TYPE_WEB
55
from api.models.certificate import Certificate
66
from api.models.domain import Domain
77
from api.tests import TEST_ROOT, DryccTestCase
@@ -26,8 +26,10 @@ def setUp(self):
2626
self.url = '/v2/certs'
2727
self.app = App.objects.create(owner=self.user, id='test-app-use-case-3')
2828
self.domains = {
29-
'foo.com': Domain.objects.create(owner=self.user, app=self.app, domain='foo.com'),
30-
'bar.com': Domain.objects.create(owner=self.user, app=self.app, domain='bar.com'),
29+
'foo.com': Domain.objects.create(
30+
owner=self.user, app=self.app, domain='foo.com', procfile_type=PROCFILE_TYPE_WEB),
31+
'bar.com': Domain.objects.create(
32+
owner=self.user, app=self.app, domain='bar.com', procfile_type=PROCFILE_TYPE_WEB),
3133
}
3234

3335
self.certificates = {}

rootfs/api/tests/test_certificate_use_case_4.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.contrib.auth import get_user_model
22
from django.core.cache import cache
33

4-
from api.models.app import App
4+
from api.models.app import App, PROCFILE_TYPE_WEB
55
from api.models.certificate import Certificate
66
from api.models.domain import Domain
77
from api.tests import TEST_ROOT, DryccTestCase
@@ -26,9 +26,13 @@ def setUp(self):
2626
self.url = '/v2/certs'
2727
self.app = App.objects.create(owner=self.user, id='test-app-use-case-3')
2828
self.domains = {
29-
'*.foo.com': Domain.objects.create(owner=self.user, app=self.app, domain='*.foo.com'),
30-
'foo.com': Domain.objects.create(owner=self.user, app=self.app, domain='foo.com'),
31-
'bar.com': Domain.objects.create(owner=self.user, app=self.app, domain='bar.com'),
29+
'*.foo.com': Domain.objects.create(
30+
owner=self.user, app=self.app, domain='*.foo.com',
31+
procfile_type=PROCFILE_TYPE_WEB),
32+
'foo.com': Domain.objects.create(
33+
owner=self.user, app=self.app, domain='foo.com', procfile_type=PROCFILE_TYPE_WEB),
34+
'bar.com': Domain.objects.create(
35+
owner=self.user, app=self.app, domain='bar.com', procfile_type=PROCFILE_TYPE_WEB),
3236
}
3337

3438
self.certificates = {}

rootfs/api/tests/test_certificate_use_case_5.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.contrib.auth import get_user_model
22
from django.core.cache import cache
33

4-
from api.models.app import App
4+
from api.models.app import App, PROCFILE_TYPE_WEB
55
from api.models.certificate import Certificate
66
from api.models.domain import Domain
77
from api.tests import TEST_ROOT, DryccTestCase
@@ -26,10 +26,14 @@ def setUp(self):
2626
self.url = '/v2/certs'
2727
self.app = App.objects.create(owner=self.user, id='test-app-use-case-5')
2828
# Done out of scope as it gets the same cert as the wildcard
29-
Domain.objects.create(owner=self.user, app=self.app, domain='foo.com')
29+
Domain.objects.create(
30+
owner=self.user, app=self.app, domain='foo.com', procfile_type=PROCFILE_TYPE_WEB)
3031
self.domains = {
31-
'*.foo.com': Domain.objects.create(owner=self.user, app=self.app, domain='*.foo.com'),
32-
'bar.com': Domain.objects.create(owner=self.user, app=self.app, domain='bar.com'),
32+
'*.foo.com': Domain.objects.create(
33+
owner=self.user, app=self.app, domain='*.foo.com',
34+
procfile_type=PROCFILE_TYPE_WEB),
35+
'bar.com': Domain.objects.create(
36+
owner=self.user, app=self.app, domain='bar.com', procfile_type=PROCFILE_TYPE_WEB),
3337
}
3438

3539
self.certificates = {}

0 commit comments

Comments
 (0)