Repository: freedomofpress/securedrop

Issue:
securedrop-admin check_for_updates is showing update needed when it should not
## Description

./securedrop-admin check_for_updates is showing update needed when it should not


## Steps to Reproduce

```
amnesia@amnesia:~/Persistent/securedrop$ git checkout develop
Switched to branch 'develop'
Your branch is up-to-date with 'origin/develop'.
amnesia@amnesia:~/Persistent/securedrop$ git tag -a 0.10.0
amnesia@amnesia:~/Persistent/securedrop$ git checkout 0.8.0-rc3
Note: checking out '0.8.0-rc3'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 20289641... SecureDrop 0.8.0~rc3
amnesia@amnesia:~/Persistent/securedrop$ ./securedrop-admin check_for_updates
INFO: Checking for SecureDrop updates...
Fetching origin
INFO: Update needed
amnesia@amnesia:~/Persistent/securedrop$ git describe
0.8.0-rc3
amnesia@amnesia:~/Persistent/securedrop$ git checkout 0.10.0 
Previous HEAD position was 20289641... SecureDrop 0.8.0~rc3
HEAD is now at 4720be05... Merge pull request #3570 from dachary/wip-i18n
amnesia@amnesia:~/Persistent/securedrop$ ./securedrop-admin check_for_updates
INFO: Checking for SecureDrop updates...
Fetching origin
INFO: Update needed
amnesia@amnesia:~/Persistent/securedrop$ git describe
0.10.0
amnesia@amnesia:~/Persistent/securedrop$ 

```

## Expected Behavior

Should not say updated required.

## Actual Behavior

Saying update needed.

## Comments

Suggestions to fix, any other relevant information.



Discussion:
This impacts 0.10.0 and later:

```
amnesia@amnesia:~/Persistent/securedrop$ git tag -a 0.9.0
amnesia@amnesia:~/Persistent/securedrop$ ./securedrop-admin check_for_updates
INFO: Checking for SecureDrop updates...
Fetching origin
INFO: All updates applied
amnesia@amnesia:~/Persistent/securedrop$ git describe
0.9.0
amnesia@amnesia:~/Persistent/securedrop$ git tag -a 0.10.0
amnesia@amnesia:~/Persistent/securedrop$ ./securedrop-admin check_for_updates
INFO: Checking for SecureDrop updates...
Fetching origin
INFO: Update needed
amnesia@amnesia:~/Persistent/securedrop$ git describe
0.10.0
```

because the output of `git tag` (which is the subprocess call done in the update functionality) is not sorted:

```
0.1
0.10.0
0.2
0.2.1
0.3
0.3.1
0.3.10
0.3.11
0.3.12
0.3.2
0.3.3
0.3.4
0.3.5
0.3.6
0.3.7
0.3.7.post1
0.3.8
0.3.9
0.3pre
0.4
0.4.1
0.4.2
0.4.3
0.4.4
0.5
0.5.1
0.5.2
0.6
0.6-rc1
0.6-rc2
0.6-rc3
0.6-rc4
0.6-rc5
0.7.0
0.7.0-rc1
0.7.0-rc2
0.7.0-rc3
0.7.0-rc4
0.7.0-rc5
0.8-rc1
0.8.0
0.8.0-rc1
0.8.0-rc2
0.8.0-rc3
0.9.0
```

Ideally: We don't fix this and instead implement #3502
Since we did not make progress on #3502 in this release cycle, this bug must be fixed in 0.9.0
Implementing https://github.com/freedomofpress/securedrop/issues/3502 will take time and also not an easy task to implement and do QA a week before the release. For now I would suggest to sort the versions following `semver` sorting logic of `setuptools` and we will not need to install anything new. I will submit a PR on this.

Resolving patch:
diff --git a/admin/securedrop_admin/__init__.py b/admin/securedrop_admin/__init__.py
--- a/admin/securedrop_admin/__init__.py
+++ b/admin/securedrop_admin/__init__.py
@@ -36,6 +36,7 @@
 import prompt_toolkit
 from prompt_toolkit.validation import Validator, ValidationError
 import yaml
+from pkg_resources import parse_version
 
 sdlog = logging.getLogger(__name__)
 RELEASE_KEY = '22245C81E3BAEB4138B36061310F561200F4AD77'
@@ -628,6 +629,9 @@ def check_for_updates(args):
     # Do not check out any release candidate tags
     all_prod_tags = [x for x in all_tags if 'rc' not in x]
 
+    # We want the tags to be sorted based on semver
+    all_prod_tags.sort(key=parse_version)
+
     latest_tag = all_prod_tags[-1]
 
     if current_tag != latest_tag:


Test patch:
diff --git a/admin/tests/test_securedrop-admin.py b/admin/tests/test_securedrop-admin.py
--- a/admin/tests/test_securedrop-admin.py
+++ b/admin/tests/test_securedrop-admin.py
@@ -66,6 +66,20 @@ def test_check_for_updates_update_needed(self, tmpdir, caplog):
                 assert update_status is True
                 assert tag == '0.6.1'
 
+    def test_check_for_updates_higher_version(self, tmpdir, caplog):
+        git_repo_path = str(tmpdir)
+        args = argparse.Namespace(root=git_repo_path)
+        current_tag = "0.6"
+        tags_available = "0.1\n0.10.0\n0.6.2\n0.6\n0.6-rc1\n0.9.0\n"
+
+        with mock.patch('subprocess.check_call'):
+            with mock.patch('subprocess.check_output',
+                            side_effect=[current_tag, tags_available]):
+                update_status, tag = securedrop_admin.check_for_updates(args)
+                assert "Update needed" in caplog.text
+                assert update_status is True
+                assert tag == '0.10.0'
+
     def test_check_for_updates_ensure_newline_stripped(self, tmpdir, caplog):
         """Regression test for #3426"""
         git_repo_path = str(tmpdir)