Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

roll-to-release #11

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions gonzo/tasks/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from fabric.api import task, env, sudo, put, run, local, settings
from fabric.context_managers import prefix as fab_prefix, hide
from fabric.contrib.files import exists
from fabric.utils import abort

from gonzo.config import PROJECT_ROOT, local_state
from gonzo.utils import last_index
Expand Down Expand Up @@ -352,8 +353,15 @@ def purge_release(release):


@task
def rollback():
""" Roll back to the most recent active release in the history file.
def rollback(release=None):
""" Either roll back to the most recent active release in the history file
or to the specified release.

Specifying a ``release`` SHA will roll back to the given release
provided it is in the history list and purge all subsequent releases so
that it is the most current. Providing a partial SHA or other ref will
use ``git rev-parse`` to expand the full SHA so that it can be looked
up in the history list.

This is used for quick recovery in case of bugs discovered shortly
after activating a new release.
Expand All @@ -362,16 +370,22 @@ def rollback():
code should be restarted following this command.
"""

current_release = get_current()
if release:
# Attempt to roll back to the specified release
release = rev_parse(release)
if release not in list_releases():
abort('release ({}) not found in release list'.format(release))

previous_release = get_previous_release(current_release)
else:
current_release = get_current()
release = get_previous_release(current_release)

if not previous_release:
raise RuntimeError("No release to roll back to")
if not release:
abort("No release to roll back to")

# sadly, there's not way to make this atomic
set_current(previous_release)
rollback_history(previous_release)
set_current(release)
rollback_history(release)


@task
Expand Down
41 changes: 39 additions & 2 deletions tests/test_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,17 +116,54 @@ def test_rollback(get_current, set_current):
assert_called_once_with(set_current, 'aaa')


@patch('gonzo.tasks.release.abort')
@patch('gonzo.tasks.release.set_current')
@patch('gonzo.tasks.release.get_current')
def test_rollback_nowhere_to_go(get_current, set_current):
def test_rollback_nowhere_to_go(get_current, set_current, abort):
get_current.return_value = 'aaa'
abort.side_effect = ValueError
with mock_history(initial=['aaa']) as releases:
with pytest.raises(RuntimeError):
with pytest.raises(ValueError):
rollback()
assert abort.called is True
assert releases == ['aaa']
assert_has_calls(set_current, [])


@patch('gonzo.tasks.release.abort')
@patch('gonzo.tasks.release.set_current')
@patch('gonzo.tasks.release.rev_parse')
def test_rollback_to_invalid_release(rev_parse, set_current, abort):
rev_parse.return_value = 'bbb'
abort.side_effect = ValueError
with mock_history(initial=['aaa']) as releases:
with pytest.raises(ValueError):
rollback('bbb')
assert abort.called is True
assert releases == ['aaa']
assert_has_calls(set_current, [])


@patch('gonzo.tasks.release.set_current')
@patch('gonzo.tasks.release.rev_parse')
def test_rollback_to_previous_release(rev_parse, set_current):
rev_parse.return_value = 'aaa'
with mock_history(initial=['aaa', 'bbb']) as releases:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could possible use one more history item here to illustrate we're going back more than to 'previous'

rollback('aaa')
assert releases == ['aaa']
assert_called_once_with(set_current, 'aaa')


@patch('gonzo.tasks.release.set_current')
@patch('gonzo.tasks.release.rev_parse')
def test_rollback_to_release(rev_parse, set_current):
rev_parse.return_value = 'aaa'
with mock_history(initial=['aaa', 'bbb', 'ccc']) as releases:
rollback('aaa')
assert releases == ['aaa']
assert_called_once_with(set_current, 'aaa')


@patch('gonzo.tasks.release.get_commit')
@patch('gonzo.tasks.release.set_current')
@patch('gonzo.tasks.release.get_current')
Expand Down