Skip to content
This repository has been archived by the owner on Nov 30, 2021. It is now read-only.

Commit

Permalink
Added support for deis release:rollback, fixes #382.
Browse files Browse the repository at this point in the history
  • Loading branch information
mboersma committed Jan 8, 2014
1 parent f021b55 commit f7d887d
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 12 deletions.
7 changes: 1 addition & 6 deletions api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -776,11 +776,6 @@ class Meta:
def __str__(self):
return "{0}-v{1}".format(self.app.id, self.version)

def rollback(self):
# create a rollback log entry
# call run
raise NotImplementedError


@receiver(release_signal)
def new_release(sender, **kwargs):
Expand All @@ -791,7 +786,7 @@ def new_release(sender, **kwargs):
Releases start at v1 and auto-increment.
"""
user, app, = kwargs['user'], kwargs['app']
last_release = Release.objects.filter(app=app).order_by('-created')[0]
last_release = app.release_set.latest()
config = kwargs.get('config', last_release.config)
build = kwargs.get('build', last_release.build)
# overwrite config with build.config if the keys don't exist
Expand Down
73 changes: 67 additions & 6 deletions api/tests/test_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from __future__ import unicode_literals

import json
import unittest
import uuid

from django.test import TestCase
Expand Down Expand Up @@ -136,19 +135,81 @@ def test_release(self):
self.assertEqual(self.client.delete(url).status_code, 405)
return release3

@unittest.expectedFailure
def test_release_rollback(self):
url = '/api/apps'
body = {'formation': 'autotest'}
response = self.client.post(url, json.dumps(body), content_type='application/json')
self.assertEqual(response.status_code, 201)
app_id = response.data['id']
# check to see that an initial release was created
# try to rollback with only 1 release extant, expecting 404
url = "/api/apps/{app_id}/releases/rollback/".format(**locals())
response = self.client.post(url, content_type='application/json')
self.assertEqual(response.status_code, 404)
# update config to roll a new release
url = '/api/apps/{app_id}/config'.format(**locals())
body = {'values': json.dumps({'NEW_URL1': 'http://localhost:8080/'})}
response = self.client.post(
url, json.dumps(body), content_type='application/json')
self.assertEqual(response.status_code, 201)
# update the build to roll a new release
url = '/api/apps/{app_id}/builds'.format(**locals())
build_config = json.dumps({'PATH': 'bin:/usr/local/bin:/usr/bin:/bin'})
body = {
'sha': uuid.uuid4().hex,
'slug_size': 4096000,
'procfile': json.dumps({'web': 'node server.js'}),
'url':
'http://deis.local/slugs/1c52739bbf3a44d3bfb9a58f7bbdd5fb.tar.gz',
'checksum': uuid.uuid4().hex, 'config': build_config,
}
response = self.client.post(
url, json.dumps(body), content_type='application/json')
self.assertEqual(response.status_code, 201)
# rollback and check to see that a 4th release was created
# with the build and config of release #2
url = "/api/apps/{app_id}/releases/rollback/".format(**locals())
response = self.client.post(url, content_type='application/json')
self.assertEqual(response.status_code, 201)
url = '/api/apps/{app_id}/releases'.format(**locals())
response = self.client.get(url, content_type='application/json')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['count'], 4)
url = '/api/apps/{app_id}/releases/2'.format(**locals())
response = self.client.get(url, content_type='application/json')
self.assertEqual(response.status_code, 200)
release2 = response.data
self.assertEquals(release2['version'], 2)
url = '/api/apps/{app_id}/releases/4'.format(**locals())
response = self.client.get(url, content_type='application/json')
self.assertEqual(response.status_code, 200)
release4 = response.data
self.assertEquals(release4['version'], 4)
self.assertNotEqual(release2['uuid'], release4['uuid'])
self.assertEqual(release2['build'], release4['build'])
self.assertEqual(release2['config'], release4['config'])
# rollback explicitly to release #1 and check that a 5th release
# was created with the build and config of release #1
url = "/api/apps/{app_id}/releases/rollback/".format(**locals())
body = {'version': 1}
response = self.client.post(
url, json.dumps(body), content_type='application/json')
self.assertEqual(response.status_code, 201)
url = '/api/apps/{app_id}/releases'.format(**locals())
response = self.client.get(url, content_type='application/json')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['count'], 5)
url = '/api/apps/{app_id}/releases/1'.format(**locals())
response = self.client.get(url)
uuid = response.data['results'][0]['uuid']
release = Release.objects.get(uuid=uuid)
release.rollback() # raises NotImplementedError currently
self.assertEqual(response.status_code, 200)
release1 = response.data
url = '/api/apps/{app_id}/releases/5'.format(**locals())
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
release5 = response.data
self.assertEqual(release5['version'], 5)
self.assertNotEqual(release1['uuid'], release5['uuid'])
self.assertEqual(release1['build'], release5['build'])
self.assertEqual(release1['config'], release5['config'])

def test_release_str(self):
"""Test the text representation of a release."""
Expand Down
6 changes: 6 additions & 0 deletions api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@
List all :class:`~api.models.Release`\s.
.. http:post:: /api/apps/(string:id)/releases/rollback/
Rollback to a previous :class:`~api.models.Release`.
Application Infrastructure
--------------------------
Expand Down Expand Up @@ -409,6 +413,8 @@
views.AppBuildViewSet.as_view({'get': 'list', 'post': 'create'})),
url(r'^apps/(?P<id>[-_\w]+)/releases/(?P<version>[0-9]+)/?',
views.AppReleaseViewSet.as_view({'get': 'retrieve'})),
url(r'^apps/(?P<id>[-_\w]+)/releases/rollback/?',
views.AppReleaseViewSet.as_view({'post': 'rollback'})),
url(r'^apps/(?P<id>[-_\w]+)/releases/?',
views.AppReleaseViewSet.as_view({'get': 'list'})),
# application infrastructure
Expand Down
19 changes: 19 additions & 0 deletions api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from Crypto.PublicKey import RSA
from django.contrib.auth.models import AnonymousUser
from django.contrib.auth.models import User
from django.db import transaction
from django.utils import timezone
from guardian.shortcuts import assign_perm
from guardian.shortcuts import get_objects_for_user
Expand Down Expand Up @@ -591,6 +592,24 @@ def get_object(self, *args, **kwargs):
"""Get Release by version always."""
return self.get_queryset(**kwargs).get(version=self.kwargs['version'])

def rollback(self, request, *args, **kwargs):
"""
Create a new release as a copy of the state of the compiled slug and
config vars of a previous release.
"""
app = get_object_or_404(models.App, id=self.kwargs['id'])
last_version = app.release_set.latest().version
version = request.DATA.get('version', last_version - 1)
if version < 1:
return Response(status=status.HTTP_404_NOT_FOUND)
prev = app.release_set.get(version=version)
with transaction.atomic():
app.release_set.create(owner=request.user, version=last_version + 1,
build=prev.build, config=prev.config)
app.converge()
msg = "Rolled back to {}".format(version)
return Response(msg, status=status.HTTP_201_CREATED)


class AppContainerViewSet(OwnerViewSet):
"""RESTful views for :class:`~api.models.Container`."""
Expand Down
22 changes: 22 additions & 0 deletions client/deis.py
Original file line number Diff line number Diff line change
Expand Up @@ -1954,6 +1954,27 @@ def releases_list(self, args):
else:
raise ResponseError(response)

def releases_rollback(self, args):
"""
Roll back to a previous application release.
Usage: deis releases:rollback [--app=<app>] [<version>]
"""
app = args.get('--app')
if not app:
app = self._session.app
version = args.get('--version')
if version:
body = {'version': version}
else:
body = {}
url = "/api/apps/{app}/releases/rollback".format(**locals())
response = self._dispatch('post', url, json.dumps(body))
if response.status_code == requests.codes.created:
print(response.json())
else:
raise ResponseError(response)


def parse_args(cmd):
"""
Expand All @@ -1973,6 +1994,7 @@ def parse_args(cmd):
'ssh': 'nodes:ssh',
'open': 'apps:open',
'logs': 'apps:logs',
'rollback': 'releases:rollback',
'run': 'apps:run',
'sharing': 'perms:list',
'sharing:list': 'perms:list',
Expand Down

0 comments on commit f7d887d

Please sign in to comment.