diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE
index a77ccb28e..c2d62322d 100644
--- a/.github/PULL_REQUEST_TEMPLATE
+++ b/.github/PULL_REQUEST_TEMPLATE
@@ -4,5 +4,5 @@ requires deis/workflow#1234
requires deis/workflow-e2e#5678
-[docs]: https://github.com/deis/workflow
-[e2e]: https://github.com/deis/workflow-e2e
+[docs]: https://github.com/deisthree/workflow
+[e2e]: https://github.com/deisthree/workflow-e2e
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..ce3dde90e
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,8 @@
+sudo: required
+language: go
+services:
+- docker
+script: make test
+notifications:
+ slack:
+ secure: pWDCV3od8gxvzxh9DrOTvBL54XoCfWYhZZlwd2ZbyyOz6SS12Psg/ZuCT2253p4yMfF/LPlsz76mr7NgcCrMI0ReveTa/rnt3XBZtyY+1rlsQsy2oxgdAzbO587ENCQeMw2F/OWHaixMT8NDqxEqQd6xafK9Zmg6BeBjwgs7XfXKcR3WzNIuCO0ZG05+Yd0FIxmd/8Xm5tGiFEYr05+Ix6MLdF9MSCXZUPeu1EsYXhDljokLq49w63W1UMU10tm4t7VCEdaO+X9w6EJ5Ov8HDxb6L6IviUYY6+IGTZ01nwIoM6OrGQqfEAytYqgTKdehgQzQnAbLI6TW2wJ0twqEsLrlbTa4NW4j0KkazQJkN5kqcKYQvaeKJJhvJIG44Gi/u78pW3S6W7NU5DhrlE6bbxdIBHJW1vJBimkqu2oBNrO5ZoBB9MS9zflBsU5g/pQpVeHWMnWE8fcYDGa1PqAcr7q6wtdPsrVZhnHmmARN3PwZzIVVVsXbaIQG8VLC5grLGnwMf1Y1fz2nK3sVpCftvrYZT3G6CNAASo+eLOwYdZdiJ9jIS7WNLN1GtpIEvjeDt3QRqsDyH8YoAKUvY5h/v8IWPP/BaSwQbJwep4+Dj7xkpXX5/4wm4jEnVFV1p4xE0lD1AXvEMAVHtPhhggvscNhF9j6oeoPju6eTPcxG+5o=
diff --git a/Makefile b/Makefile
index ed689ab33..2e0d65a6e 100644
--- a/Makefile
+++ b/Makefile
@@ -58,7 +58,7 @@ test-functional:
@echo "Implement functional tests in _tests directory"
test-integration:
- @echo "Check https://github.com/deis/workflow-e2e for the complete integration test suite"
+ @echo "Check https://github.com/deisthree/workflow-e2e for the complete integration test suite"
upload-coverage:
$(eval CI_ENV := $(shell curl -s https://codecov.io/env | bash))
diff --git a/README.md b/README.md
index ea0c0a578..9417cf063 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,4 @@
-| | Deis Workflow will soon no longer be maintained.
Please [read the announcement](https://deis.com/blog/2017/deis-workflow-final-release/) for more detail. |
-|---:|---|
-| 09/07/2017 | Deis Workflow [v2.18][] final release before entering maintenance mode |
-| 03/01/2018 | End of Workflow maintenance: critical patches no longer merged |
-
# Deis Controller
[](https://ci.deis.io/job/controller)
@@ -13,7 +8,7 @@
Deis (pronounced DAY-iss) Workflow is an open source Platform as a Service (PaaS) that adds a developer-friendly layer to any [Kubernetes](http://kubernetes.io) cluster, making it easy to deploy and manage applications on your own servers.
-For more information about the Deis Workflow, please visit the main project page at https://github.com/deis/workflow.
+For more information about the Deis Workflow, please visit the main project page at https://github.com/deisthree/workflow.
We welcome your input! If you have feedback, please [submit an issue][issues]. If you'd like to participate in development, please read the "Development" section below and [submit a pull request][prs].
@@ -82,8 +77,8 @@ kubectl get pod --namespace=deis -w | grep deis-controller
```
[install-k8s]: https://kubernetes.io/docs/setup/pick-right-solution
-[issues]: https://github.com/deis/controller/issues
-[prs]: https://github.com/deis/controller/pulls
-[workflow]: https://github.com/deis/workflow
+[issues]: https://github.com/deisthree/controller/issues
+[prs]: https://github.com/deisthree/controller/pulls
+[workflow]: https://github.com/deisthree/workflow
[Docker]: https://www.docker.com/
-[v2.18]: https://github.com/deis/workflow/releases/tag/v2.18.0
+[v2.18]: https://github.com/deisthree/workflow/releases/tag/v2.18.0
diff --git a/charts/controller/Chart.yaml b/charts/controller/Chart.yaml
index 7071a1c51..b7928fe8b 100644
--- a/charts/controller/Chart.yaml
+++ b/charts/controller/Chart.yaml
@@ -1,5 +1,5 @@
name: controller
-home: https://github.com/deis/controller
+home: https://github.com/deisthree/controller
version:
description: Deis Workflow Controller (API).
maintainers:
diff --git a/charts/controller/templates/_helpers.tmpl b/charts/controller/templates/_helpers.tmpl
index 0b9deb080..422a5ae3f 100644
--- a/charts/controller/templates/_helpers.tmpl
+++ b/charts/controller/templates/_helpers.tmpl
@@ -1,10 +1,12 @@
{{/*
-Set apiVersion based on Kubernetes version
+Set apiVersion based on .Capabilities.APIVersions
*/}}
{{- define "rbacAPIVersion" -}}
-{{- if ge .Capabilities.KubeVersion.Minor "6" -}}
+{{- if .Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1beta1" -}}
rbac.authorization.k8s.io/v1beta1
-{{- else -}}
+{{- else if .Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1alpha1" -}}
rbac.authorization.k8s.io/v1alpha1
+{{- else -}}
+rbac.authorization.k8s.io/v1
{{- end -}}
{{- end -}}
diff --git a/charts/controller/templates/controller-clusterrole.yaml b/charts/controller/templates/controller-clusterrole.yaml
index d0a08470a..53c934d72 100644
--- a/charts/controller/templates/controller-clusterrole.yaml
+++ b/charts/controller/templates/controller-clusterrole.yaml
@@ -34,7 +34,7 @@ rules:
verbs: ["get"]
- apiGroups: [""]
resources: ["pods"]
- verbs: ["get", "list", "delete"]
+ verbs: ["get", "list", "create", "delete"]
- apiGroups: [""]
resources: ["resourcequotas"]
verbs: ["get", "create"]
diff --git a/rootfs/api/migrations/0024_config_lifecycle_hooks.py b/rootfs/api/migrations/0024_config_lifecycle_hooks.py
new file mode 100644
index 000000000..c23d80f9c
--- /dev/null
+++ b/rootfs/api/migrations/0024_config_lifecycle_hooks.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.2 on 2017-06-22 18:42
+from __future__ import unicode_literals
+
+from django.db import migrations
+import jsonfield.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0023_app_k8s_name_length'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='config',
+ name='lifecycle_post_start',
+ field=jsonfield.fields.JSONField(blank=True, default={}),
+ ),
+ migrations.AddField(
+ model_name='config',
+ name='lifecycle_pre_stop',
+ field=jsonfield.fields.JSONField(blank=True, default={}),
+ ),
+ ]
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 219bb6c2c..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 = {}
@@ -1086,6 +1099,8 @@ def _gather_app_settings(self, release, app_settings, process_type, replicas):
'app_type': process_type,
'build_type': release.build.type,
'healthcheck': healthcheck,
+ 'lifecycle_post_start': config.lifecycle_post_start,
+ 'lifecycle_pre_stop': config.lifecycle_pre_stop,
'routable': routable,
'deploy_batches': batches,
'deploy_timeout': deploy_timeout,
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/config.py b/rootfs/api/models/config.py
index 2e69006d5..4ab1145e6 100644
--- a/rootfs/api/models/config.py
+++ b/rootfs/api/models/config.py
@@ -18,6 +18,8 @@ class Config(UuidAuditedModel):
app = models.ForeignKey('App', on_delete=models.CASCADE)
values = JSONField(default={}, blank=True)
memory = JSONField(default={}, blank=True)
+ lifecycle_post_start = JSONField(default={}, blank=True)
+ lifecycle_pre_stop = JSONField(default={}, blank=True)
cpu = JSONField(default={}, blank=True)
tags = JSONField(default={}, blank=True)
registry = JSONField(default={}, blank=True)
@@ -162,7 +164,8 @@ def save(self, **kwargs):
# usually means a totally new app
previous_config = self.app.config_set.latest()
- for attr in ['cpu', 'memory', 'tags', 'registry', 'values']:
+ for attr in ['cpu', 'memory', 'tags', 'registry', 'values',
+ 'lifecycle_post_start', 'lifecycle_pre_stop']:
data = getattr(previous_config, attr, {}).copy()
new_data = getattr(self, attr, {}).copy()
diff --git a/rootfs/api/models/release.py b/rootfs/api/models/release.py
index 1ccbbd436..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
@@ -424,6 +427,40 @@ def save(self, *args, **kwargs): # noqa
changes = 'changed limits for '+', '.join(changes)
self.summary += "{} {}".format(self.config.owner, changes)
+ # if the lifecycle_post_start hooks changed, log the dict diff
+ changes = []
+ old_lifecycle_post_start = old_config.lifecycle_post_start if old_config else {}
+ diff = dict_diff(self.config.lifecycle_post_start, old_lifecycle_post_start)
+ # try to be as succinct as possible
+ added = ', '.join(k for k in diff.get('added', {}))
+ added = 'added lifecycle_post_start ' + added if added else ''
+ changed = ', '.join(k for k in diff.get('changed', {}))
+ changed = 'changed lifecycle_post_start ' + changed if changed else ''
+ deleted = ', '.join(k for k in diff.get('deleted', {}))
+ deleted = 'deleted lifecycle_post_start ' + deleted if deleted else ''
+ changes = ', '.join(i for i in (added, changed, deleted) if i)
+ if changes:
+ if self.summary:
+ self.summary += ' and '
+ self.summary += "{} {}".format(self.config.owner, changes)
+
+ # if the lifecycle_pre_stop hooks changed, log the dict diff
+ changes = []
+ old_lifecycle_pre_stop = old_config.lifecycle_pre_stop if old_config else {}
+ diff = dict_diff(self.config.lifecycle_pre_stop, old_lifecycle_pre_stop)
+ # try to be as succinct as possible
+ added = ', '.join(k for k in diff.get('added', {}))
+ added = 'added lifecycle_pre_stop ' + added if added else ''
+ changed = ', '.join(k for k in diff.get('changed', {}))
+ changed = 'changed lifecycle_pre_stop ' + changed if changed else ''
+ deleted = ', '.join(k for k in diff.get('deleted', {}))
+ deleted = 'deleted lifecycle_pre_stop ' + deleted if deleted else ''
+ changes = ', '.join(i for i in (added, changed, deleted) if i)
+ if changes:
+ if self.summary:
+ self.summary += ' and '
+ self.summary += "{} {}".format(self.config.owner, changes)
+
# if the tags changed, log the dict diff
changes = []
old_tags = old_config.tags if old_config else {}
diff --git a/rootfs/api/serializers.py b/rootfs/api/serializers.py
index 46a46fb77..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):
@@ -210,6 +211,8 @@ class ConfigSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
values = JSONFieldSerializer(required=False, binary=True)
memory = JSONFieldSerializer(required=False, binary=True)
+ lifecycle_post_start = JSONFieldSerializer(required=False, binary=True)
+ lifecycle_pre_stop = JSONFieldSerializer(required=False, binary=True)
cpu = JSONFieldSerializer(required=False, binary=True)
tags = JSONFieldSerializer(required=False, binary=True)
registry = JSONFieldSerializer(required=False, binary=True)
@@ -244,7 +247,7 @@ def validate_values(self, data):
if key == 'HEALTHCHECK_URL':
# Only Path information is supported, not query / anchor or anything else
# Path is the only thing Kubernetes supports right now
- # See https://github.com/deis/controller/issues/774
+ # See https://github.com/deisthree/controller/issues/774
uri = urlparse(value)
if not uri.path:
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_app_settings.py b/rootfs/api/tests/test_app_settings.py
index 27ef76d97..b587f5607 100644
--- a/rootfs/api/tests/test_app_settings.py
+++ b/rootfs/api/tests/test_app_settings.py
@@ -276,7 +276,7 @@ def test_settings_labels(self, mock_requests):
base_labels = {
'label':
{
- 'git_repo': 'https://github.com/deis/controller',
+ 'git_repo': 'https://github.com/deisthree/controller',
'team': 'frontend',
'empty': ''
}
diff --git a/rootfs/api/tests/test_auth.py b/rootfs/api/tests/test_auth.py
index 41d7efcea..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/deis/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_config.py b/rootfs/api/tests/test_config.py
index c65721af3..160af515e 100644
--- a/rootfs/api/tests/test_config.py
+++ b/rootfs/api/tests/test_config.py
@@ -165,7 +165,8 @@ def test_response_data(self, mock_requests):
response = self.client.post(url, body)
for key in response.data:
self.assertIn(key, ['uuid', 'owner', 'created', 'updated', 'app', 'values', 'memory',
- 'cpu', 'tags', 'registry', 'healthcheck'])
+ 'cpu', 'tags', 'registry', 'healthcheck', 'lifecycle_post_start',
+ 'lifecycle_pre_stop'])
expected = {
'owner': self.user.username,
'app': app_id,
@@ -188,7 +189,8 @@ def test_response_data_types_converted(self, mock_requests):
self.assertEqual(response.status_code, 201, response.data)
for key in response.data:
self.assertIn(key, ['uuid', 'owner', 'created', 'updated', 'app', 'values', 'memory',
- 'cpu', 'tags', 'registry', 'healthcheck'])
+ 'cpu', 'tags', 'registry', 'healthcheck', 'lifecycle_post_start',
+ 'lifecycle_pre_stop'])
expected = {
'owner': self.user.username,
'app': app_id,
@@ -340,7 +342,7 @@ def test_admin_can_create_config_on_other_apps(self, mock_requests):
def test_config_owner_is_requesting_user(self, mock_requests):
"""
Ensure that setting the config value is owned by the requesting user
- See https://github.com/deis/deis/issues/2650
+ See https://github.com/deisthree/deis/issues/2650
"""
response = self.test_admin_can_create_config_on_other_apps()
self.assertEqual(response.data['owner'], self.user.username)
diff --git a/rootfs/api/tests/test_domain.py b/rootfs/api/tests/test_domain.py
index 47d831eb7..9a024ab79 100644
--- a/rootfs/api/tests/test_domain.py
+++ b/rootfs/api/tests/test_domain.py
@@ -237,7 +237,7 @@ def test_delete_domain_does_not_exist(self):
self.assertEqual(response.status_code, 404)
def test_delete_domain_does_not_remove_latest(self):
- """https://github.com/deis/deis/issues/3239"""
+ """https://github.com/deisthree/deis/issues/3239"""
url = '/v2/apps/{app_id}/domains'.format(app_id=self.app_id)
test_domains = [
'test-domain.example.com',
@@ -255,7 +255,7 @@ def test_delete_domain_does_not_remove_latest(self):
Domain.objects.get(domain=test_domains[0])
def test_delete_domain_does_not_remove_others(self):
- """https://github.com/deis/deis/issues/3475"""
+ """https://github.com/deisthree/deis/issues/3475"""
self.test_delete_domain_does_not_remove_latest()
self.assertEqual(Domain.objects.all().count(), 2)
diff --git a/rootfs/api/tests/test_limits.py b/rootfs/api/tests/test_limits.py
index d7b1f44aa..144c7bef8 100644
--- a/rootfs/api/tests/test_limits.py
+++ b/rootfs/api/tests/test_limits.py
@@ -64,7 +64,7 @@ def test_request_limit_memory(self, mock_requests):
self.assertEqual(response.status_code, 200, response.data)
self.assertIn('memory', response.data)
self.assertEqual(response.data['memory'], {})
- # regression test for https://github.com/deis/deis/issues/1563
+ # regression test for https://github.com/deisthree/deis/issues/1563
self.assertNotIn('"', response.data['memory'])
# set an initial limit
@@ -105,7 +105,7 @@ def test_request_limit_memory(self, mock_requests):
self.assertIn('web', memory)
self.assertEqual(memory['web'], '1G')
- # regression test for https://github.com/deis/deis/issues/1613
+ # regression test for https://github.com/deisthree/deis/issues/1613
# ensure that config:set doesn't wipe out previous limits
body = {'values': json.dumps({'NEW_URL2': 'http://localhost:8080/'})}
response = self.client.post(url, body)
@@ -193,7 +193,7 @@ def test_request_limit_cpu(self, mock_requests):
self.assertEqual(response.status_code, 200, response.data)
self.assertIn('cpu', response.data)
self.assertEqual(response.data['cpu'], {})
- # regression test for https://github.com/deis/deis/issues/1563
+ # regression test for https://github.com/deisthree/deis/issues/1563
self.assertNotIn('"', response.data['cpu'])
# set an initial limit
diff --git a/rootfs/api/tests/test_pods.py b/rootfs/api/tests/test_pods.py
index 978c97240..ecc48ca93 100644
--- a/rootfs/api/tests/test_pods.py
+++ b/rootfs/api/tests/test_pods.py
@@ -325,7 +325,7 @@ def test_container_str(self, mock_requests):
self.assertRegex(pod['name'], app_id + '-(worker|web)-[0-9]{8,10}-[a-z0-9]{5}')
def test_pod_command_format(self, mock_requests):
- # regression test for https://github.com/deis/deis/pull/1285
+ # regression test for https://github.com/deisthree/deis/pull/1285
app_id = self.create_app()
# post a new build
@@ -580,7 +580,7 @@ def test_run_command_good(self, mock_requests):
def test_run_not_fail_on_debug(self, mock_requests):
"""
- do a run with DEIS_DEBUG on - https://github.com/deis/controller/issues/583
+ do a run with DEIS_DEBUG on - https://github.com/deisthree/controller/issues/583
"""
env = EnvironmentVarGuard()
env['DEIS_DEBUG'] = 'true'
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/tests/test_tls.py b/rootfs/api/tests/test_tls.py
index aba25aca0..fb09d95a0 100644
--- a/rootfs/api/tests/test_tls.py
+++ b/rootfs/api/tests/test_tls.py
@@ -66,7 +66,7 @@ def test_tls_created_on_app_create(self, mock_requests):
"""
Ensure that a TLS object is created for an App with default values.
- See https://github.com/deis/controller/issues/1042
+ See https://github.com/deisthree/controller/issues/1042
"""
app_id = self.create_app()
response = self.client.get('/v2/apps/{}/tls'.format(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()
diff --git a/rootfs/dev_requirements.txt b/rootfs/dev_requirements.txt
index 4d4b2df0a..0281c4cf9 100644
--- a/rootfs/dev_requirements.txt
+++ b/rootfs/dev_requirements.txt
@@ -9,7 +9,7 @@ codecov==2.0.9
# mock out python-requests, mostly k8s
# requests-mock==1.3.0
-git+https://github.com/deis/requests-mock.git@class_adapter#egg=request_mock
+git+https://github.com/deisthree/requests-mock.git@class_adapter#egg=request_mock
# tail a log and pipe into tbgrep to find all tracebacks
tbgrep==0.3.0
diff --git a/rootfs/scheduler/resources/pod.py b/rootfs/scheduler/resources/pod.py
index b8ad48c65..0d3ada9ee 100644
--- a/rootfs/scheduler/resources/pod.py
+++ b/rootfs/scheduler/resources/pod.py
@@ -224,6 +224,8 @@ def _set_container(self, namespace, container_name, data, **kwargs):
self._set_health_checks(data, env, **kwargs)
+ self._set_lifecycle_hooks(data, env, **kwargs)
+
def _set_resources(self, container, kwargs):
""" Set CPU/memory resource management manifest """
app_type = kwargs.get("app_type")
@@ -278,6 +280,38 @@ def _set_health_checks(self, container, env, **kwargs):
elif kwargs.get('routable', False):
self._default_readiness_probe(container, kwargs.get('build_type'), env.get('PORT', None)) # noqa
+ def _set_lifecycle_hooks(self, container, env, **kwargs):
+ app_type = kwargs.get("app_type")
+ lifecycle_post_start = kwargs.get('lifecycle_post_start', {})
+ lifecycle_post_start = lifecycle_post_start.get(app_type)
+ lifecycle_pre_stop = kwargs.get('lifecycle_pre_stop', {})
+ lifecycle_pre_stop = lifecycle_pre_stop.get(app_type)
+ lifecycle = defaultdict(dict)
+ if lifecycle_post_start or lifecycle_pre_stop:
+ lifecycle = defaultdict(dict)
+
+ if lifecycle_post_start:
+ lifecycle["postStart"] = {
+ 'exec': {
+ "command": [
+ "/bin/bash",
+ "-c",
+ "{0}".format(lifecycle_post_start)
+ ]
+ }
+ }
+ if lifecycle_pre_stop:
+ lifecycle["preStop"] = {
+ 'exec': {
+ "command": [
+ "/bin/bash",
+ "-c",
+ "{0}".format(lifecycle_pre_stop)
+ ]
+ }
+ }
+ container["lifecycle"] = dict(lifecycle)
+
def _default_readiness_probe(self, container, build_type, port=None):
# Update only the application container with the health check
if build_type == "buildpack":
@@ -290,11 +324,11 @@ def _default_readiness_probe(self, container, build_type, port=None):
http://kubernetes.io/docs/user-guide/pod-states/#container-probes
/runner/init is the entry point of the slugrunner.
- https://github.com/deis/slugrunner/blob/01eac53f1c5f1d1dfa7570bbd6b9e45c00441fea/rootfs/Dockerfile#L20
+ https://github.com/deisthree/slugrunner/blob/01eac53f1c5f1d1dfa7570bbd6b9e45c00441fea/rootfs/Dockerfile#L20
Once it downloads the slug it starts running using `exec` which means the pid 1
will point to the slug/application command instead of entry point once the application has
started.
- https://github.com/deis/slugrunner/blob/01eac53f1c5f1d1dfa7570bbd6b9e45c00441fea/rootfs/runner/init#L90
+ https://github.com/deisthree/slugrunner/blob/01eac53f1c5f1d1dfa7570bbd6b9e45c00441fea/rootfs/runner/init#L90
This should be added only for the build pack apps when a custom liveness probe is not set to
make sure that the pod is ready only when the slug is downloaded and started running.
@@ -345,6 +379,15 @@ def _default_dockerapp_readiness_probe(self, port, delay=5, timeout=5, period_se
}
return readinessprobe
+ def _set_custom_termination_period(self, container, period_seconds=900):
+ """
+ Applies a custom terminationGracePeriod only if provided as env variable.
+ """
+ terminationperiod = {
+ 'terminationGracePeriodSeconds': int(period_seconds)
+ }
+ container.update(terminationperiod)
+
def delete(self, namespace, name):
# get timeout info from pod
pod = self.pod.get(namespace, name).json()