diff options
author | Todd Gamblin <tgamblin@llnl.gov> | 2020-01-21 23:50:59 -0800 |
---|---|---|
committer | Todd Gamblin <tgamblin@llnl.gov> | 2020-01-22 17:34:31 -0800 |
commit | 8011fedd9ca47578e8da37a9060407c6784d7015 (patch) | |
tree | 6ddc17d10abbb56d03f8c7260a1bb4af45dfc4f5 | |
parent | 910df8cb4e05e990f308f90154fa3b1b3f24741b (diff) | |
download | spack-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.
-rw-r--r-- | lib/spack/spack/test/cmd/gpg.py | 41 | ||||
-rw-r--r-- | lib/spack/spack/test/conftest.py | 15 | ||||
-rw-r--r-- | lib/spack/spack/util/gpg.py | 46 |
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.""" |