Skip to content

Commit

Permalink
Add branch information and troubleshooting tips
Browse files Browse the repository at this point in the history
  • Loading branch information
eloquence committed Feb 18, 2021
1 parent a152219 commit 033a9a6
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 4 deletions.
30 changes: 29 additions & 1 deletion admin/securedrop_admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,11 +720,23 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:

update_status, latest_tag = check_for_updates(cli_args)
if update_status is True:

# Useful for troubleshooting
branch_status = get_git_branch(cli_args)

sdlog.error("You are not running the most recent signed SecureDrop release "
"on this workstation.")
sdlog.error("Latest available version: {}".format(latest_tag))

if branch_status is not None:
sdlog.error("Current branch status: {}".format(branch_status))
else:
sdlog.error("Problem determining current branch status.")

sdlog.error("Running outdated or mismatched code can cause significant "
"technical issues.")
sdlog.error("To display more information about your repository state, run:\n\n\t"
"git status\n")
sdlog.error("If you are certain you want to proceed, run:\n\n\t"
"./securedrop-admin --force {}\n".format(cmd_name))
sdlog.error("To apply the latest updates, run:\n\n\t"
Expand Down Expand Up @@ -905,7 +917,10 @@ def check_for_updates(args: argparse.Namespace) -> Tuple[bool, str]:
"""Check for SecureDrop updates"""
sdlog.info("Checking for SecureDrop updates...")

# Determine what branch we are on
# Determine what tag we are likely to be on. Caveat: git describe
# may produce very surprising results, because it will locate the most recent
# _reachable_ tag. However, in our current branching model, it can be
# relied on to determine if we're on the latest tag or not.
current_tag = subprocess.check_output(['git', 'describe'],
cwd=args.root).decode('utf-8').rstrip('\n') # noqa: E501

Expand Down Expand Up @@ -933,6 +948,19 @@ def check_for_updates(args: argparse.Namespace) -> Tuple[bool, str]:
return False, latest_tag


def get_git_branch(args: argparse.Namespace) -> Optional[str]:
"""
Returns the starred line of `git branch` output.
"""
git_branch_raw = subprocess.check_output(['git', 'branch'],
cwd=args.root).decode('utf-8')
match = re.search(r"\* (.*)\n", git_branch_raw)
if match is not None and len(match.groups()) > 0:
return match.group(1)
else:
return None


def get_release_key_from_keyserver(
args: argparse.Namespace, keyserver: Optional[str] = None, timeout: int = 45
) -> None:
Expand Down
48 changes: 45 additions & 3 deletions admin/tests/test_securedrop-admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ def test_update_check_decorator_when_no_update_needed(self, caplog):
"""
with mock.patch(
"securedrop_admin.check_for_updates", side_effect=[[False, "1.5.0"]]
) as mocked_check, mock.patch("sys.exit") as mocked_exit:
) as mocked_check, mock.patch(
"securedrop_admin.get_git_branch", side_effect=["develop"]
), mock.patch("sys.exit") as mocked_exit:
# The decorator itself interprets --force
args = argparse.Namespace(force=False)
rv = securedrop_admin.update_check_required("update_check_test")(
Expand All @@ -85,11 +87,14 @@ def test_update_check_decorator_when_update_needed(self, caplog):
And an update is required
Then the update check should run to completion
And an error referencing the command should be displayed
And the current branch state should be included in the output
And the program should exit
"""
with mock.patch(
"securedrop_admin.check_for_updates", side_effect=[[True, "1.5.0"]]
) as mocked_check, mock.patch("sys.exit") as mocked_exit:
) as mocked_check, mock.patch(
"securedrop_admin.get_git_branch", side_effect=["bad_branch"]
), mock.patch("sys.exit") as mocked_exit:
# The decorator itself interprets --force
args = argparse.Namespace(force=False)
securedrop_admin.update_check_required("update_check_test")(
Expand All @@ -98,6 +103,7 @@ def test_update_check_decorator_when_update_needed(self, caplog):
assert mocked_check.called
assert mocked_exit.called
assert "update_check_test" in caplog.text
assert "bad_branch" in caplog.text

def test_update_check_decorator_when_skipped(self, caplog):
"""
Expand All @@ -110,7 +116,9 @@ def test_update_check_decorator_when_skipped(self, caplog):
"""
with mock.patch(
"securedrop_admin.check_for_updates", side_effect=[[True, "1.5.0"]]
) as mocked_check, mock.patch("sys.exit") as mocked_exit:
) as mocked_check, mock.patch(
"securedrop_admin.get_git_branch", side_effect=["develop"]
), mock.patch("sys.exit") as mocked_exit:
# The decorator itself interprets --force
args = argparse.Namespace(force=True)
rv = securedrop_admin.update_check_required("update_check_test")(
Expand Down Expand Up @@ -194,6 +202,40 @@ def test_check_for_updates_if_most_recent_tag_is_rc(self, tmpdir, caplog):
assert update_status is False
assert tag == '0.6.1'

@pytest.mark.parametrize(
"git_output, expected_rv",
[
(b'* develop\n',
'develop'),
(b' develop\n'
b'* release/1.7.0\n',
'release/1.7.0'),
(b'* (HEAD detached at 1.7.0)\n'
b' develop\n'
b' release/1.7.0\n',
'(HEAD detached at 1.7.0)'),
(b' main\n'
b'* valid_+!@#$%&_branch_name\n',
'valid_+!@#$%&_branch_name'),
(b'Unrecognized output.',
None)
]
)
def test_get_git_branch(self, git_output, expected_rv):
"""
When `git branch` completes with exit code 0
And the output conforms to the expected format
Then `get_git_branch` should return a description of the current HEAD
When `git branch` completes with exit code 0
And the output does not conform to the expected format
Then `get_git_branch` should return `None`
"""
args = argparse.Namespace(root=None)
with mock.patch('subprocess.check_output', side_effect=[git_output]):
rv = securedrop_admin.get_git_branch(args)
assert rv == expected_rv

def test_update_exits_if_not_needed(self, tmpdir, caplog):
git_repo_path = str(tmpdir)
args = argparse.Namespace(root=git_repo_path)
Expand Down

0 comments on commit 033a9a6

Please sign in to comment.