summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2020-01-21 23:50:59 -0800
committerTodd Gamblin <tgamblin@llnl.gov>2020-01-22 17:34:31 -0800
commit8011fedd9ca47578e8da37a9060407c6784d7015 (patch)
tree6ddc17d10abbb56d03f8c7260a1bb4af45dfc4f5 /lib
parent910df8cb4e05e990f308f90154fa3b1b3f24741b (diff)
downloadspack-8011fedd9ca47578e8da37a9060407c6784d7015.tar.gz
spack-8011fedd9ca47578e8da37a9060407c6784d7015.tar.bz2
spack-8011fedd9ca47578e8da37a9060407c6784d7015.tar.xz
spack-8011fedd9ca47578e8da37a9060407c6784d7015.zip
bugfix: gpg2 is called 'gpg' on macOS
The gpg2 command isn't always around; it's sometimes called gpg. This is the case with the brew-installed version, and it's breaking our tests. - [x] Look for both 'gpg2' and 'gpg' when finding the command - [x] If we find 'gpg', ensure the version is 2 or higher - [x] Add tests for version detection.
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/test/cmd/gpg.py41
-rw-r--r--lib/spack/spack/test/conftest.py15
-rw-r--r--lib/spack/spack/util/gpg.py46
3 files changed, 89 insertions, 13 deletions
diff --git a/lib/spack/spack/test/cmd/gpg.py b/lib/spack/spack/test/cmd/gpg.py
index 2b63fbdb29..4333a38fe2 100644
--- a/lib/spack/spack/test/cmd/gpg.py
+++ b/lib/spack/spack/test/cmd/gpg.py
@@ -7,6 +7,9 @@ import os
import pytest
+import llnl.util.filesystem as fs
+
+import spack.util.executable
import spack.util.gpg
from spack.paths import mock_gpg_data_path, mock_gpg_keys_path
@@ -14,15 +17,45 @@ from spack.main import SpackCommand
from spack.util.executable import ProcessError
-@pytest.fixture(scope='function')
-def gpg():
- return SpackCommand('gpg')
+#: spack command used by tests below
+gpg = SpackCommand('gpg')
+
+
+# test gpg command detection
+@pytest.mark.parametrize('cmd_name,version', [
+ ('gpg', 'undetectable'), # undetectable version
+ ('gpg', 'gpg (GnuPG) 1.3.4'), # insufficient version
+ ('gpg', 'gpg (GnuPG) 2.2.19'), # sufficient version
+ ('gpg2', 'gpg (GnuPG) 2.2.19'), # gpg2 command
+])
+def test_find_gpg(cmd_name, version, tmpdir, mock_gnupghome, monkeypatch):
+ with tmpdir.as_cwd():
+ with open(cmd_name, 'w') as f:
+ f.write("""\
+#!/bin/sh
+echo "{version}"
+""".format(version=version))
+ fs.set_executable(cmd_name)
+
+ monkeypatch.setitem(os.environ, "PATH", str(tmpdir))
+ if version == 'undetectable' or version.endswith('1.3.4'):
+ with pytest.raises(spack.util.gpg.SpackGPGError):
+ exe = spack.util.gpg.Gpg.gpg()
+ else:
+ exe = spack.util.gpg.Gpg.gpg()
+ assert isinstance(exe, spack.util.executable.Executable)
+
+
+def test_no_gpg_in_path(tmpdir, mock_gnupghome, monkeypatch):
+ monkeypatch.setitem(os.environ, "PATH", str(tmpdir))
+ with pytest.raises(spack.util.gpg.SpackGPGError):
+ spack.util.gpg.Gpg.gpg()
@pytest.mark.maybeslow
@pytest.mark.skipif(not spack.util.gpg.Gpg.gpg(),
reason='These tests require gnupg2')
-def test_gpg(gpg, tmpdir, mock_gnupghome):
+def test_gpg(tmpdir, mock_gnupghome):
# Verify a file with an empty keyring.
with pytest.raises(ProcessError):
gpg('verify', os.path.join(mock_gpg_data_path, 'content.txt'))
diff --git a/lib/spack/spack/test/conftest.py b/lib/spack/spack/test/conftest.py
index b40dd92f99..8b8d128d2c 100644
--- a/lib/spack/spack/test/conftest.py
+++ b/lib/spack/spack/test/conftest.py
@@ -11,6 +11,7 @@ import itertools
import os
import os.path
import shutil
+import tempfile
import xml.etree.ElementTree
import ordereddict_backport
@@ -674,9 +675,19 @@ def module_configuration(monkeypatch, request):
@pytest.fixture()
-def mock_gnupghome(tmpdir, monkeypatch):
- monkeypatch.setattr(spack.util.gpg, 'GNUPGHOME', str(tmpdir.join('gpg')))
+def mock_gnupghome(monkeypatch):
+ # GNU PGP can't handle paths longer than 108 characters (wtf!@#$) so we
+ # have to make our own tmpdir with a shorter name than pytest's.
+ # This comes up because tmp paths on macOS are already long-ish, and
+ # pytest makes them longer.
+ short_name_tmpdir = tempfile.mkdtemp()
+ monkeypatch.setattr(spack.util.gpg, 'GNUPGHOME', short_name_tmpdir)
+ monkeypatch.setattr(spack.util.gpg.Gpg, '_gpg', None)
+ yield
+
+ # clean up, since we are doing this manually
+ shutil.rmtree(short_name_tmpdir)
##########
# Fake archives and repositories
diff --git a/lib/spack/spack/util/gpg.py b/lib/spack/spack/util/gpg.py
index 93d79d1e11..29b2add852 100644
--- a/lib/spack/spack/util/gpg.py
+++ b/lib/spack/spack/util/gpg.py
@@ -4,10 +4,14 @@
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os
+import re
+import spack.error
import spack.paths
-from spack.util.executable import Executable
+import spack.version
+from spack.util.executable import which
+_gnupg_version_re = r"^gpg \(GnuPG\) (.*)$"
GNUPGHOME = spack.paths.gpg_path
@@ -28,15 +32,39 @@ def parse_keys_output(output):
class Gpg(object):
+ _gpg = None
+
@staticmethod
def gpg():
# TODO: Support loading up a GPG environment from a built gpg.
- gpg = Executable('gpg2')
- if not os.path.exists(GNUPGHOME):
- os.makedirs(GNUPGHOME)
- os.chmod(GNUPGHOME, 0o700)
- gpg.add_default_env('GNUPGHOME', GNUPGHOME)
- return gpg
+ if Gpg._gpg is None:
+ gpg = which('gpg2', 'gpg')
+
+ if not gpg:
+ raise SpackGPGError("Spack requires gpg version 2 or higher.")
+
+ # ensure that the version is actually >= 2 if we find 'gpg'
+ if gpg.name == 'gpg':
+ output = gpg('--version', output=str)
+ match = re.search(_gnupg_version_re, output, re.M)
+
+ if not match:
+ raise SpackGPGError("Couldn't determine version of gpg")
+
+ v = spack.version.Version(match.group(1))
+ if v < spack.version.Version('2'):
+ raise SpackGPGError("Spack requires GPG version >= 2")
+
+ # make the GNU PG path if we need to
+ # TODO: does this need to be in the spack directory?
+ # we should probably just use GPG's regular conventions
+ if not os.path.exists(GNUPGHOME):
+ os.makedirs(GNUPGHOME)
+ os.chmod(GNUPGHOME, 0o700)
+ gpg.add_default_env('GNUPGHOME', GNUPGHOME)
+
+ Gpg._gpg = gpg
+ return Gpg._gpg
@classmethod
def create(cls, **kwargs):
@@ -112,3 +140,7 @@ class Gpg(object):
cls.gpg()('--list-public-keys')
if signing:
cls.gpg()('--list-secret-keys')
+
+
+class SpackGPGError(spack.error.SpackError):
+ """Class raised when GPG errors are detected."""