Skip to content

Commit ffa5ca3

Browse files
committed
feat(rootfs/api): implement api for services.
implement api, models, serializers for services
1 parent 2bf9a23 commit ffa5ca3

6 files changed

Lines changed: 138 additions & 0 deletions

File tree

rootfs/api/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ class Meta:
142142
from .certificate import Certificate, validate_certificate # noqa
143143
from .config import Config # noqa
144144
from .domain import Domain # noqa
145+
from .service import Service # noqa
145146
from .key import Key, validate_base64 # noqa
146147
from .release import Release # noqa
147148
from .tls import TLS # noqa

rootfs/api/models/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class Config(UuidAuditedModel):
2424
tags = JSONField(default={}, blank=True)
2525
registry = JSONField(default={}, blank=True)
2626
healthcheck = JSONField(default={}, blank=True)
27+
termination_grace_period = JSONField(default={}, blank=True)
2728

2829
class Meta:
2930
get_latest_by = 'created'

rootfs/api/models/service.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from django.db import models
2+
from django.conf import settings
3+
4+
from api.models import AuditedModel
5+
6+
class Service(AuditedModel):
7+
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
8+
app = models.ForeignKey('App', on_delete=models.CASCADE)
9+
procfile_type = models.TextField(blank=False, null=False, unique=False)
10+
path_pattern = models.TextField(blank=False, null=False, unique=False)
11+
12+
class Meta:
13+
get_latest_by = 'created'
14+
unique_together = (('app', 'procfile_type'))
15+
ordering = ['-created']
16+
17+
def __str__(self):
18+
return "{}-{}".format(self.app.id, str(self.procfile_type))
19+
20+
def as_dict(self):
21+
return {
22+
"procfile_type": self.procfile_type,
23+
"path_pattern": self.path_pattern
24+
}
25+
26+
def save(self, *args, **kwargs):
27+
# app = str(self.app)
28+
# domain = str(self.domain)
29+
30+
# # get config for the service
31+
# config = self._load_service_config(app, 'router')
32+
33+
# # See if domains are available
34+
# if 'domains' not in config:
35+
# config['domains'] = ''
36+
37+
# # convert from string to list to work with and filter out empty strings
38+
# domains = [_f for _f in config['domains'].split(',') if _f]
39+
# if domain not in domains:
40+
# domains.append(domain)
41+
# config['domains'] = ','.join(domains)
42+
43+
# self._save_service_config(app, 'router', config)
44+
45+
# Save to DB
46+
return super(Service, self).save(*args, **kwargs)
47+
48+
def delete(self, *args, **kwargs):
49+
# app = str(self.app)
50+
# domain = str(self.domain)
51+
52+
# # Deatch cert, updates k8s
53+
# if self.certificate:
54+
# self.certificate.detach(domain=domain)
55+
56+
# # get config for the service
57+
# config = self._load_service_config(app, 'router')
58+
59+
# # See if domains are available
60+
# if 'domains' not in config:
61+
# config['domains'] = ''
62+
63+
# # convert from string to list to work with and filter out empty strings
64+
# domains = [_f for _f in config['domains'].split(',') if _f]
65+
# if domain in domains:
66+
# domains.remove(domain)
67+
# config['domains'] = ','.join(domains)
68+
69+
# self._save_service_config(app, 'router', config)
70+
71+
# Delete from DB
72+
return super(Service, self).delete(*args, **kwargs)

rootfs/api/serializers.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,39 @@ def ToACE(x): return idna.alabel(x).decode("utf-8", "strict")
454454

455455
return aceValue
456456

457+
class ServiceSerializer(serializers.ModelSerializer):
458+
"""Serialize a :class:`~api.models.Service` model."""
459+
460+
app = serializers.SlugRelatedField(slug_field='id', queryset=models.App.objects.all())
461+
owner = serializers.ReadOnlyField(source='owner.username')
462+
procfile_type = serializers.CharField(allow_blank=False, allow_null=False, required=True)
463+
path_pattern = serializers.CharField(allow_blank=False, allow_null=False, required=True)
464+
465+
class Meta:
466+
"""Metadata options for a :class:`ServiceSerializer`."""
467+
model = models.Service
468+
fields = ['owner', 'created', 'updated', 'app', 'procfile_type', 'path_pattern']
469+
read_only_fields = ['uuid']
470+
471+
def validate_procfile_type(self, value):
472+
if not re.match(PROCTYPE_MATCH, value):
473+
raise serializers.ValidationError(PROCTYPE_MISMATCH_MSG)
474+
475+
return value
476+
477+
def validate_path_pattern(self, value):
478+
for pattern in str(value).split(","):
479+
if not pattern.strip():
480+
raise serializers.ValidationError(
481+
"Service value should be valid regex (or set of regex split by comma)")
482+
try:
483+
re.compile(pattern)
484+
except re.error as e:
485+
raise serializers.ValidationError(
486+
"Service value should be valid regex (or set of regex split by comma), error: {}"\
487+
.format(e.message))
488+
489+
return value
457490

458491
class CertificateSerializer(serializers.ModelSerializer):
459492
"""Serialize a :class:`~api.models.Cert` model."""

rootfs/api/urls.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
views.DomainViewSet.as_view({'delete': 'destroy'})),
5151
url(r"^apps/(?P<id>{})/domains/?$".format(settings.APP_URL_REGEX),
5252
views.DomainViewSet.as_view({'post': 'create', 'get': 'list'})),
53+
# application services
54+
url(r"^apps/(?P<id>{})/services/?$".format(settings.APP_URL_REGEX),
55+
views.ServiceViewSet.as_view({'post': 'create_or_update', 'get': 'list', 'delete': 'delete'})),
5356
# application actions
5457
url(r"^apps/(?P<id>{})/scale/?$".format(settings.APP_URL_REGEX),
5558
views.AppViewSet.as_view({'post': 'scale'})),

rootfs/api/views.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,34 @@ def get_object(self, **kwargs):
371371
ace_domain = domain
372372
return get_object_or_404(qs, domain=ace_domain)
373373

374+
class ServiceViewSet(AppResourceViewSet):
375+
"""A viewset for interacting with Service objects."""
376+
model = models.Service
377+
serializer_class = serializers.ServiceSerializer
378+
379+
def list(self, *args, **kwargs):
380+
services = self.get_app().service_set.all()
381+
data = [ obj.as_dict() for obj in services ]
382+
return Response(data, status=status.HTTP_200_OK)
383+
384+
def create_or_update(self, request, **kwargs):
385+
pft = self.get_serializer().validate_procfile_type(request.data.get('procfile_type'))
386+
pp = self.get_serializer().validate_path_pattern(request.data.get('path_pattern'))
387+
svc = self.get_app().service_set.filter(procfile_type=pft).first()
388+
if svc:
389+
if svc.path_pattern == pp:
390+
return Response(status=status.HTTP_204_NO_CONTENT)
391+
else:
392+
svc.path_pattern = pp
393+
svc.save()
394+
return Response(status=status.HTTP_201_CREATED)
395+
396+
def delete(self, request, **kwargs):
397+
pft = self.get_serializer().validate_procfile_type(request.data.get('procfile_type'))
398+
qs = self.get_queryset(**kwargs)
399+
svc = get_object_or_404(qs, procfile_type=pft)
400+
svc.delete()
401+
return Response(status=status.HTTP_204_NO_CONTENT)
374402

375403
class CertificateViewSet(BaseDeisViewSet):
376404
"""A viewset for interacting with Certificate objects."""

0 commit comments

Comments
 (0)