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.
This commit is contained in:
parent
910df8cb4e
commit
8011fedd9c
@ -7,6 +7,9 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
import llnl.util.filesystem as fs
|
||||||
|
|
||||||
|
import spack.util.executable
|
||||||
import spack.util.gpg
|
import spack.util.gpg
|
||||||
|
|
||||||
from spack.paths import mock_gpg_data_path, mock_gpg_keys_path
|
from spack.paths import mock_gpg_data_path, mock_gpg_keys_path
|
||||||
@ -14,15 +17,45 @@
|
|||||||
from spack.util.executable import ProcessError
|
from spack.util.executable import ProcessError
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
#: spack command used by tests below
|
||||||
def gpg():
|
gpg = SpackCommand('gpg')
|
||||||
return 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.maybeslow
|
||||||
@pytest.mark.skipif(not spack.util.gpg.Gpg.gpg(),
|
@pytest.mark.skipif(not spack.util.gpg.Gpg.gpg(),
|
||||||
reason='These tests require gnupg2')
|
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.
|
# Verify a file with an empty keyring.
|
||||||
with pytest.raises(ProcessError):
|
with pytest.raises(ProcessError):
|
||||||
gpg('verify', os.path.join(mock_gpg_data_path, 'content.txt'))
|
gpg('verify', os.path.join(mock_gpg_data_path, 'content.txt'))
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import shutil
|
import shutil
|
||||||
|
import tempfile
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
import ordereddict_backport
|
import ordereddict_backport
|
||||||
@ -674,9 +675,19 @@ def writer_key_function():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def mock_gnupghome(tmpdir, monkeypatch):
|
def mock_gnupghome(monkeypatch):
|
||||||
monkeypatch.setattr(spack.util.gpg, 'GNUPGHOME', str(tmpdir.join('gpg')))
|
# 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
|
# Fake archives and repositories
|
||||||
|
@ -4,10 +4,14 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
import spack.error
|
||||||
import spack.paths
|
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
|
GNUPGHOME = spack.paths.gpg_path
|
||||||
|
|
||||||
@ -28,15 +32,39 @@ def parse_keys_output(output):
|
|||||||
|
|
||||||
|
|
||||||
class Gpg(object):
|
class Gpg(object):
|
||||||
|
_gpg = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def gpg():
|
def gpg():
|
||||||
# TODO: Support loading up a GPG environment from a built gpg.
|
# TODO: Support loading up a GPG environment from a built gpg.
|
||||||
gpg = Executable('gpg2')
|
if Gpg._gpg is None:
|
||||||
if not os.path.exists(GNUPGHOME):
|
gpg = which('gpg2', 'gpg')
|
||||||
os.makedirs(GNUPGHOME)
|
|
||||||
os.chmod(GNUPGHOME, 0o700)
|
if not gpg:
|
||||||
gpg.add_default_env('GNUPGHOME', GNUPGHOME)
|
raise SpackGPGError("Spack requires gpg version 2 or higher.")
|
||||||
return gpg
|
|
||||||
|
# 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
|
@classmethod
|
||||||
def create(cls, **kwargs):
|
def create(cls, **kwargs):
|
||||||
@ -112,3 +140,7 @@ def list(cls, trusted, signing):
|
|||||||
cls.gpg()('--list-public-keys')
|
cls.gpg()('--list-public-keys')
|
||||||
if signing:
|
if signing:
|
||||||
cls.gpg()('--list-secret-keys')
|
cls.gpg()('--list-secret-keys')
|
||||||
|
|
||||||
|
|
||||||
|
class SpackGPGError(spack.error.SpackError):
|
||||||
|
"""Class raised when GPG errors are detected."""
|
||||||
|
Loading…
Reference in New Issue
Block a user