diff --git a/rootfs/api/models/app.py b/rootfs/api/models/app.py index 426637cb..429304f6 100644 --- a/rootfs/api/models/app.py +++ b/rootfs/api/models/app.py @@ -269,7 +269,7 @@ def restart(self, **kwargs): # noqa def scale(self, user, structure): err_msg = None - release = self.release_set.filter(failed=False).latest() + release = Release.latest(self) if (PTYPE_RUN in structure or release.build is None): if PTYPE_RUN in structure: err_msg = 'Cannot set scale for reserved types, procfile type is: run' @@ -348,9 +348,9 @@ def deploy(self, release, ptypes=None, force_deploy=False, rollback_on_failure=T release.clean(ptypes) def mount(self, user, volume, structure=None): - if self.release_set.filter(failed=False).latest().build is None: + release = Release.latest(self) + if release is None or release.build is None: raise DryccException('No build associated with this release') - release = self.release_set.filter(failed=False).latest() app_settings = self.appsettings_set.latest() self._mount(user, volume, release, app_settings, structure=structure) @@ -387,8 +387,8 @@ def pod_name(size=5, chars=string.ascii_lowercase + string.digits): return ''.join(random.choice(chars) for _ in range(size)) """Run a one-off command in an ephemeral app container.""" - release = self.release_set.filter(failed=False).latest() - if release.build is None: + release = Release.latest(self) + if release is None or release.build is None: raise DryccException('No build associated with this release to run this command') app_settings = self.appsettings_set.latest() @@ -656,8 +656,8 @@ def image_pull_secret(self, namespace, registry, image): return name def state_to_k8s(self): - release = self.release_set.filter(failed=False).latest() - if release.build is None: + release = Release.latest(self) + if release is None or release.build is None: self.log('the last release does not have a build, skipping deployment...') return ptypes = set() diff --git a/rootfs/api/models/build.py b/rootfs/api/models/build.py index f00b2b58..663ca485 100644 --- a/rootfs/api/models/build.py +++ b/rootfs/api/models/build.py @@ -4,6 +4,7 @@ from api.exceptions import DryccException from .base import UuidAuditedModel, PTYPE_WEB from .config import Config +from .release import Release User = get_user_model() logger = logging.getLogger(__name__) @@ -72,7 +73,7 @@ def get_image(self, ptype, default_image=None): return default_image if default_image else self.image def create_release(self, user, *args, **kwargs): - latest_release = self.app.release_set.filter(failed=False).latest() + latest_release = Release.latest(self.app) try: new_release = latest_release.new( user, @@ -109,7 +110,7 @@ def _get_or_create_config(self): """ dryccfile to config """ - latest_release = self.app.release_set.filter(failed=False).latest() + latest_release = Release.latest(self.app) config_values, config_values_ref, config_healthcheck = [], {}, {} for group, values in self.dryccfile.get('config', {}).items(): for value in values: diff --git a/rootfs/api/models/config.py b/rootfs/api/models/config.py index 9be81c3b..7b57ae06 100644 --- a/rootfs/api/models/config.py +++ b/rootfs/api/models/config.py @@ -125,9 +125,10 @@ def save(self, ignore_update_fields=None, *args, **kwargs): """merge the old config with the new""" try: # Get config from the latest available release - try: - previous_config = self.app.release_set.filter(failed=False).latest().config - except Release.DoesNotExist: + latest_releases = Release.latest(self.app) + if latest_releases: + previous_config = latest_releases.config + else: # If that doesn't exist then fallback on app config # usually means a totally new app previous_config = self.app.config_set.latest() @@ -174,7 +175,7 @@ def _update_values(self, previous_config): else: # force to string new_item['value'] = str(new_item['value']) break - if added and new_item['value'] is not None: + if added and new_item['value'] is not None and new_item['value'] != "": data.append(new_item) setattr(self, 'values', data) diff --git a/rootfs/api/models/gateway.py b/rootfs/api/models/gateway.py index ad0aad94..9312f63b 100644 --- a/rootfs/api/models/gateway.py +++ b/rootfs/api/models/gateway.py @@ -64,7 +64,7 @@ def addresses(self): def listeners(self): auto_tls = self.app.tls_set.latest().certs_auto_enabled listeners = [] - domains = list(self._get_tls_domain(auto_tls)) + domains = list(self.app.domain_set.all()) for item in self.ports: port, protocol = item["port"], item["protocol"] if item["protocol"] in HOSTNAME_PROTOCOLS: @@ -146,12 +146,6 @@ def _check_port(self, port, protocol): return False return True - def _get_tls_domain(self, auto_tls): - domains = self.app.domain_set.all() - if not auto_tls: - domains = domains.exclude(certificate=None) - return domains - def _get_listener_name(self, port, protocol, index): if protocol in ("TCP", "TLS", "HTTP"): protocol = "TCP" diff --git a/rootfs/api/models/release.py b/rootfs/api/models/release.py index aeb0898c..d85fcae9 100644 --- a/rootfs/api/models/release.py +++ b/rootfs/api/models/release.py @@ -211,28 +211,31 @@ def deploy(self, ptypes=None, force_deploy=False): raise DryccException(f"{msg}: {lock.locked(ptypes)}") run_pipeline.delay(self, ptypes, force_deploy) - def previous(self, state="succeed"): - """ - Return the previous Release to this one. - - :return: the previous :class:`Release`, or None - """ - releases = self.app.release_set - if self.pk: - releases = releases.exclude(pk=self.pk) + @classmethod + def latest(cls, app, state=None, exclude_pk=None): + releases = app.release_set + if exclude_pk: + releases = releases.exclude(pk=exclude_pk) q = Q(failed=False, state="succeed") if state else Q(failed=False) try: - app_settings = self.app.appsettings_set.latest() + app_settings = app.appsettings_set.latest() if app_settings.autorollback is False: - q = Q(state__in=["crashed", "succeed"]) + q = Q() except AppSettings.DoesNotExist: pass try: # Get the Release previous to this one - prev_release = releases.filter(q).latest() + release = releases.filter(q).latest() except Release.DoesNotExist: - prev_release = None - return prev_release + release = None + return release + + def previous(self, state="succeed"): + """ + Return the previous Release to this one. + :return: the previous :class:`Release`, or None + """ + return self.latest(self.app, state, self.pk) def rollback(self, user, ptypes=None, version=None): try: diff --git a/rootfs/api/tests/test_gateway.py b/rootfs/api/tests/test_gateway.py index 7bbc13cb..f0904fea 100644 --- a/rootfs/api/tests/test_gateway.py +++ b/rootfs/api/tests/test_gateway.py @@ -328,14 +328,24 @@ def test_change_tls(self): app_id = self.create_app_with_domain_and_deploy() response = self.client.get('/v2/apps/{}/gateways/'.format(app_id)) expect = [{ - 'allowedRoutes': { - 'namespaces': { - 'from': 'All' + "allowedRoutes": { + "namespaces": { + "from": "All" } }, - 'name': 'tcp-80-0', - 'port': 80, - 'protocol': 'HTTP' + "name": "tcp-80-1", + "port": 80, + "hostname": "test-domain.example.com", + "protocol": "HTTP" + }, { + "allowedRoutes": { + "namespaces": { + "from": "All" + } + }, + "name": "tcp-80-0", + "port": 80, + "protocol": "HTTP" }] self.assertEqual(response.data["count"], 1, response.data) self.assertEqual(response.data["results"][0]["listeners"], expect, response.data) @@ -377,14 +387,24 @@ def test_change_tls(self): self.change_certs_auto(app_id, False) response = self.client.get('/v2/apps/{}/gateways/'.format(app_id)) expect = [{ - 'allowedRoutes': { - 'namespaces': { - 'from': 'All' + "allowedRoutes": { + "namespaces": { + "from": "All" + } + }, + "name": "tcp-80-1", + "port": 80, + "hostname": "test-domain.example.com", + "protocol": "HTTP" + }, { + "allowedRoutes": { + "namespaces": { + "from": "All" } }, - 'name': 'tcp-80-0', - 'port': 80, - 'protocol': 'HTTP' + "name": "tcp-80-0", + "port": 80, + "protocol": "HTTP" }] self.assertEqual(response.data["count"], 1, response.data) self.assertEqual(response.json()["results"][0]["listeners"], expect, response.data) diff --git a/rootfs/api/views.py b/rootfs/api/views.py index 7c218ec7..88720dac 100644 --- a/rootfs/api/views.py +++ b/rootfs/api/views.py @@ -279,7 +279,7 @@ def get_object(self): release = get_object_or_404( models.release.Release, app=self.get_app(), version=int(version)) else: - release = self.get_app().release_set.filter(failed=False).latest() + release = models.release.Release.latest(self.get_app()) return getattr(release, self.model.__name__.lower()) @@ -321,7 +321,7 @@ def run(self, request, **kwargs): expires = settings.KUBERNETES_JOB_MAX_TTL_SECONDS_AFTER_FINISHED if not command: raise DryccException('command is a required field, or it can be defined in Procfile') - release = app.release_set.filter(failed=False).latest() + release = models.release.Release.latest(app) if release.build is None: raise DryccException('no build available, please deploy a release') volumes = request.data.get('volumes', None) @@ -417,7 +417,7 @@ def delete(self, request, **kwargs): return Response(status=status.HTTP_200_OK) def post_save(self, config): - latest_release = config.app.release_set.filter(failed=False).latest() + latest_release = models.release.Release.latest(self.get_app()) try: release = latest_release.new( self.request.user, config=config, build=latest_release.build) @@ -673,7 +673,7 @@ def rollback(self, request, **kwargs): Create a new release as a copy of the state of the compiled slug and config vars of a previous release. """ - latest_release = self.get_app().release_set.filter(failed=False).latest() + latest_release = models.release.Release.latest(self.get_app()) ptypes = set( [ptype for ptype in request.data.get("ptypes", "").split(",") if ptype]) ptypes = latest_release.app.check_ptypes(ptypes) @@ -786,7 +786,7 @@ def create(self, request, *args, **kwargs): # return the application databag response = { 'release': { - 'version': app.release_set.filter(failed=False).latest().version + 'version': models.release.Release.latest(app).version } } return Response(response, status=status.HTTP_200_OK) @@ -807,7 +807,7 @@ def create(self, request, *args, **kwargs): has_permission, message = permissions.has_app_permission(request.user, app, request.method) if not has_permission: return Response(message, status=status.HTTP_403_FORBIDDEN) - config = app.release_set.filter(failed=False).latest().config + config = models.release.Release.latest(app).config serializer = self.get_serializer(config) return Response(serializer.data, status=status.HTTP_200_OK)