diff --git a/.travis.yml b/.travis.yml index 0a8e4c46e..0f3fd606c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ sudo: required +language: go services: - docker diff --git a/rootfs/api/migrations/0025_app_procfile_structure.py b/rootfs/api/migrations/0025_app_procfile_structure.py new file mode 100644 index 000000000..efee506e8 --- /dev/null +++ b/rootfs/api/migrations/0025_app_procfile_structure.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-08-14 20:45 +from __future__ import unicode_literals + +import api.models.app +from django.db import migrations +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0024_config_lifecycle_hooks'), + ] + + operations = [ + migrations.AddField( + model_name='app', + name='procfile_structure', + field=jsonfield.fields.JSONField(blank=True, default={}, validators=[api.models.app.validate_app_structure]), + ), + ] diff --git a/rootfs/api/migrations/0026_release_exception.py b/rootfs/api/migrations/0026_release_exception.py new file mode 100644 index 000000000..3f681cc7d --- /dev/null +++ b/rootfs/api/migrations/0026_release_exception.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-08-28 14:59 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0025_app_procfile_structure'), + ] + + operations = [ + migrations.AddField( + model_name='release', + name='exception', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/rootfs/api/models/app.py b/rootfs/api/models/app.py index 1e0c2eca5..cec6d9a3b 100644 --- a/rootfs/api/models/app.py +++ b/rootfs/api/models/app.py @@ -71,6 +71,7 @@ class App(UuidAuditedModel): validators=[validate_app_id, validate_reserved_names]) structure = JSONField(default={}, blank=True, validators=[validate_app_structure]) + procfile_structure = JSONField(default={}, blank=True, validators=[validate_app_structure]) class Meta: verbose_name = 'Application' @@ -408,6 +409,7 @@ def scale(self, user, structure): # noqa if new_structure != self.structure: # save new structure to the database self.structure = new_structure + self.procfile_structure = release.build.procfile self.save() try: @@ -474,6 +476,7 @@ def deploy(self, release, force_deploy=False, rollback_on_failure=True): # noqa # set processes structure to default if app is new. if self.structure == {}: self.structure = self._default_structure(release) + self.procfile_structure = self._default_structure(release) self.save() # reset canonical process types if build type has changed. else: @@ -489,8 +492,18 @@ def deploy(self, release, force_deploy=False, rollback_on_failure=True): # noqa # update with the default process type. structure.update(self._default_structure(release)) self.structure = structure + # if procfile structure exists then we use it + if release.build.procfile and \ + release.build.sha and not \ + release.build.dockerfile: + self.procfile_structure = release.build.procfile self.save() + # always set the procfile structure for any new release + if release.build.procfile: + self.procfile_structure = release.build.procfile + self.save() + # deploy application to k8s. Also handles initial scaling app_settings = self.appsettings_set.latest() deploys = {} diff --git a/rootfs/api/models/build.py b/rootfs/api/models/build.py index 34e58d426..924b634b0 100644 --- a/rootfs/api/models/build.py +++ b/rootfs/api/models/build.py @@ -72,6 +72,8 @@ def create(self, user, *args, **kwargs): if 'new_release' in locals(): new_release.failed = True new_release.summary = "{} deployed {} which failed".format(self.owner, str(self.uuid)[:7]) # noqa + # Get the exception that has occured + new_release.exception = "error: {}".format(str(e)) new_release.save() else: self.delete() diff --git a/rootfs/api/models/release.py b/rootfs/api/models/release.py index 330540b1e..8f98f86f5 100644 --- a/rootfs/api/models/release.py +++ b/rootfs/api/models/release.py @@ -24,6 +24,7 @@ class Release(UuidAuditedModel): version = models.PositiveIntegerField() summary = models.TextField(blank=True, null=True) failed = models.BooleanField(default=False) + exception = models.TextField(blank=True, null=True) config = models.ForeignKey('Config', on_delete=models.CASCADE) build = models.ForeignKey('Build', null=True, on_delete=models.CASCADE) @@ -243,6 +244,8 @@ def rollback(self, user, version=None): if 'new_release' in locals(): new_release.failed = True new_release.summary = "{} performed roll back to a release that failed".format(self.owner) # noqa + # Get the exception that has occured + new_release.exception = "error: {}".format(str(e)) new_release.save() raise DeisException(str(e)) from e diff --git a/rootfs/api/serializers.py b/rootfs/api/serializers.py index 501220cf0..7e0a6358e 100644 --- a/rootfs/api/serializers.py +++ b/rootfs/api/serializers.py @@ -172,11 +172,12 @@ class AppSerializer(serializers.ModelSerializer): owner = serializers.ReadOnlyField(source='owner.username') structure = serializers.JSONField(required=False) + procfile_structure = serializers.JSONField(required=False) class Meta: """Metadata options for a :class:`AppSerializer`.""" model = models.App - fields = ['uuid', 'id', 'owner', 'structure', 'created', 'updated'] + fields = ['uuid', 'id', 'owner', 'structure', 'procfile_structure', 'created', 'updated'] class BuildSerializer(serializers.ModelSerializer): diff --git a/rootfs/api/tests/test_app.py b/rootfs/api/tests/test_app.py index 40dcb4197..5b4b3bf0f 100644 --- a/rootfs/api/tests/test_app.py +++ b/rootfs/api/tests/test_app.py @@ -90,7 +90,8 @@ def test_response_data(self, mock_requests): body = {'id': 'app-{}'.format(random.randrange(1000, 10000))} response = self.client.post('/v2/apps', body) for key in response.data: - self.assertIn(key, ['uuid', 'created', 'updated', 'id', 'owner', 'structure']) + self.assertIn(key, ['uuid', 'created', 'updated', 'id', 'owner', 'structure', + 'procfile_structure']) expected = { 'id': body['id'], 'owner': self.user.username, diff --git a/rootfs/api/tests/test_auth.py b/rootfs/api/tests/test_auth.py index 0495e258c..9a826c0a5 100644 --- a/rootfs/api/tests/test_auth.py +++ b/rootfs/api/tests/test_auth.py @@ -378,5 +378,6 @@ def test_regenerate(self): def test_auth_no_ldap_by_default(self, mock_logger): """Ensure that LDAP authentication is disabled by default.""" self.test_auth() - # NOTE(bacongobbler): Using https://github.com/deisthree/controller/issues/1189 as a test case + # NOTE(bacongobbler): Using https://github.com/deisthree/controller/issues/1189 + # as a test case mock_logger.warning.assert_not_called() diff --git a/rootfs/api/tests/test_release.py b/rootfs/api/tests/test_release.py index 71e3d231b..cdd5eedb4 100644 --- a/rootfs/api/tests/test_release.py +++ b/rootfs/api/tests/test_release.py @@ -114,7 +114,7 @@ def test_response_data(self, mock_requests): response = self.client.get(url) for key in response.data.keys(): self.assertIn(key, ['uuid', 'owner', 'created', 'updated', 'app', 'build', 'config', - 'summary', 'version', 'failed']) + 'summary', 'version', 'failed', 'exception']) expected = { 'owner': self.user.username, 'app': app_id, diff --git a/rootfs/api/views.py b/rootfs/api/views.py index 86c6624db..6866fca10 100644 --- a/rootfs/api/views.py +++ b/rootfs/api/views.py @@ -289,6 +289,8 @@ def post_save(self, config): if hasattr(self, 'release'): self.release.failed = True self.release.summary = "{} deployed a config that failed".format(self.request.user) # noqa + # Get the exception that has occured + self.release.exception = "error: {}".format(str(e)) self.release.save() else: config.delete()