#6 Bundle dns
Merged 3 years ago by churchyard. Opened 3 years ago by jcapitao.
rpms/ jcapitao/python-eventlet master  into  master

file modified
+1
@@ -21,3 +21,4 @@ 

  /eventlet-0.25.2.tar.gz

  /eventlet-0.26.0.tar.gz

  /087ba743c7af8a40ac1e4e2ec89409eee3b4233e.zip

+ /dnspython-1.16.0.zip

file modified
+45 -4
@@ -2,15 +2,23 @@ 

  %global git_commit 087ba743c7af8a40ac1e4e2ec89409eee3b4233e

  %global git_date   20201102

  %global git_commit_short %(c="%{git_commit}"; echo "${c:0:8}")

+ %global bundle_dns 1

  %{?python_enable_dependency_generator}

  

  Name:           python-%{modname}

  Version:        0.29.1

- Release:        1.%{git_date}git%{git_commit_short}%{?dist}

+ Release:        2.%{git_date}git%{git_commit_short}%{?dist}

  Summary:        Highly concurrent networking library

+ %if %bundle_dns

+ License:        MIT and ISC

+ %else

  License:        MIT

+ %endif

+ 

  URL:            http://eventlet.net

  Source0:        https://github.com/eventlet/%{modname}/archive/%{git_commit}.zip

+ Source1:        %{pypi_source dnspython 1.16.0 zip}

+ Patch0:         switch_to_python_cryptography.patch

  

  BuildArch:      noarch

  
@@ -25,14 +33,24 @@ 

  BuildArch:      noarch

  BuildRequires:  python3-devel

  BuildRequires:  python3-setuptools

- BuildRequires:  python3dist(dnspython) >= 1.15

  BuildRequires:  python3dist(greenlet) >= 0.3

  BuildRequires:  python3dist(monotonic) >= 1.4

  BuildRequires:  python3dist(six) >= 1.10

  BuildRequires:  python3-nose

  BuildRequires:  python3-pyOpenSSL

+ 

+ %if %bundle_dns

+ Provides:       bundled(python3dist(dnspython)) = 1.16.0

+ BuildRequires:  python3-cryptography

+ Recommends:     python3-cryptography

+ %else

+ BuildRequires:  python3dist(dnspython) >= 1.15

+ BuildRequires:  python3dist(dnspython) < 2

+ %endif

+ 

  %{?python_provide:%python_provide python3-%{modname}}

  

+ 

  %description -n python3-%{modname}

  Eventlet is a networking library written in Python. It achieves high

  scalability by using non-blocking io while at the same time retaining
@@ -48,11 +66,28 @@ 

  %{summary}.

  

  %prep

- %autosetup -n %{modname}-%{git_commit}

- rm -vrf *.egg-info

+ %if %bundle_dns

+ %setup -n %{modname}-%{git_commit} -q -a1

+ %else

+ %setup -n %{modname}-%{git_commit} -q

+ %endif

  # Remove dependency on enum-compat from setup.py

  # enum-compat is not needed for Python 3

  sed -i "/'enum-compat',/d" setup.py

+ %if %bundle_dns

+ # We bundle last version of dns1 as eventlet does not support yet dns2

+ pushd dnspython-1.16.0

+ %patch -P 0 -p1

+ grep -lRZ "dns\." dns | xargs -0 -l sed -i -e 's/\([^[a-zA-Z]\)dns\./\1eventlet\.dns\./g'

+ grep -lRZ "^import dns$" dns | xargs -0 -l sed -i -e 's/^import\ dns$/import\ eventlet\.dns/'

+ popd

+ mv dnspython-1.16.0/dns eventlet

+ sed -i '/dnspython >= 1.15.0, < 2.0.0/d' setup.py

+ sed -i "s/import_patched('dns/import_patched('eventlet\.dns/g" eventlet/support/greendns.py

+ cp -a dnspython-1.16.0/LICENSE LICENSE.dns

+ rm -vrf dnspython-1.16.0

+ %endif

+ rm -vrf *.egg-info

  

  %build

  %py3_build
@@ -78,6 +113,9 @@ 

  %files -n python3-%{modname}

  %doc README.rst AUTHORS LICENSE NEWS

  %license LICENSE

+ %if %bundle_dns

+ %license LICENSE.dns

+ %endif

  %{python3_sitelib}/%{modname}/

  %{python3_sitelib}/%{modname}-*.egg-info/

  
@@ -86,6 +124,9 @@ 

  %doc html-3

  

  %changelog

+ * Mon Nov 30 2020 Joel Capitao <jcapitao@redhat.com> - 0.29.1-2.20201102git087ba743

+ - Bundle dns1 (rhbz#1896191)

+ 

  * Fri Nov 06 2020 Joel Capitao <jcapitao@redhat.com> - 0.29.1-1.20201102git087ba743

  - Update to 0.29.1.20201102git087ba743. (rhbz#1862178)

  

file modified
+1
@@ -1,1 +1,2 @@ 

  SHA512 (087ba743c7af8a40ac1e4e2ec89409eee3b4233e.zip) = 8f35098d67ccee8aab8c5cce45e603fade9ea5d1a704a3e6aa49e0aefa9d60246c7a07562318a7b0db26671ddfe584230b67ffb2f0c19c0fa9e61fd480a14a8d

+ SHA512 (dnspython-1.16.0.zip) = d3f71b726b7722d17b761674b44a7ca0975eeff7f9fb2fb507df0b1aaac975ddce097246340ea5809c6a7563b6851a34f43012b5cb8a37926879746b59e92575

@@ -0,0 +1,343 @@ 

+ From 087df702931f32eeb3a29957a8fe3fa31749d642 Mon Sep 17 00:00:00 2001

+ From: Simo Sorce <simo@redhat.com>

+ Date: Tue, 28 Apr 2020 10:08:02 +0200

+ Subject: [PATCH] Switch to python cryptography

+ 

+ Original patch: https://github.com/simo5/dnspython/commit/bfe84d523bd4fde7b2655857d78bba85ed05f43c

+ The same change in master: https://github.com/rthalley/dnspython/pull/449

+ ---

+  dns/dnssec.py        | 168 ++++++++++++++++++++-----------------------

+  setup.py             |   2 +-

+  tests/test_dnssec.py |  12 +---

+  3 files changed, 79 insertions(+), 103 deletions(-)

+ 

+ diff --git a/dns/dnssec.py b/dns/dnssec.py

+ index 35da6b5..73e92da 100644

+ --- a/dns/dnssec.py

+ +++ b/dns/dnssec.py

+ @@ -17,6 +17,7 @@

+ 

+  """Common DNSSEC-related functions and constants."""

+ 

+ +import hashlib  # used in make_ds() to avoid pycrypto dependency

+  from io import BytesIO

+  import struct

+  import time

+ @@ -165,10 +166,10 @@ def make_ds(name, key, algorithm, origin=None):

+ 

+      if algorithm.upper() == 'SHA1':

+          dsalg = 1

+ -        hash = SHA1.new()

+ +        hash = hashlib.sha1()

+      elif algorithm.upper() == 'SHA256':

+          dsalg = 2

+ -        hash = SHA256.new()

+ +        hash = hashlib.sha256()

+      else:

+          raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm)

+ 

+ @@ -214,7 +215,7 @@ def _is_dsa(algorithm):

+ 

+ 

+  def _is_ecdsa(algorithm):

+ -    return _have_ecdsa and (algorithm in (ECDSAP256SHA256, ECDSAP384SHA384))

+ +    return (algorithm in (ECDSAP256SHA256, ECDSAP384SHA384))

+ 

+ 

+  def _is_md5(algorithm):

+ @@ -240,18 +241,26 @@ def _is_sha512(algorithm):

+ 

+  def _make_hash(algorithm):

+      if _is_md5(algorithm):

+ -        return MD5.new()

+ +        return hashes.MD5()

+      if _is_sha1(algorithm):

+ -        return SHA1.new()

+ +        return hashes.SHA1()

+      if _is_sha256(algorithm):

+ -        return SHA256.new()

+ +        return hashes.SHA256()

+      if _is_sha384(algorithm):

+ -        return SHA384.new()

+ +        return hashes.SHA384()

+      if _is_sha512(algorithm):

+ -        return SHA512.new()

+ +        return hashes.SHA512()

+ +    if algorithm == ED25519:

+ +        return hashes.SHA512()

+ +    if algorithm == ED448:

+ +        return hashes.SHAKE256(114)

+      raise ValidationFailure('unknown hash for algorithm %u' % algorithm)

+ 

+ 

+ +def _bytes_to_long(b):

+ +    return int.from_bytes(b, 'big')

+ +

+ +

+  def _make_algorithm_id(algorithm):

+      if _is_md5(algorithm):

+          oid = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05]

+ @@ -316,8 +325,6 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):

+          if rrsig.inception > now:

+              raise ValidationFailure('not yet valid')

+ 

+ -        hash = _make_hash(rrsig.algorithm)

+ -

+          if _is_rsa(rrsig.algorithm):

+              keyptr = candidate_key.key

+              (bytes_,) = struct.unpack('!B', keyptr[0:1])

+ @@ -328,9 +335,9 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):

+              rsa_e = keyptr[0:bytes_]

+              rsa_n = keyptr[bytes_:]

+              try:

+ -                pubkey = CryptoRSA.construct(

+ -                    (number.bytes_to_long(rsa_n),

+ -                     number.bytes_to_long(rsa_e)))

+ +                public_key = rsa.RSAPublicNumbers(

+ +                    _bytes_to_long(rsa_e),

+ +                    _bytes_to_long(rsa_n)).public_key(default_backend())

+              except ValueError:

+                  raise ValidationFailure('invalid public key')

+              sig = rrsig.signature

+ @@ -346,42 +353,47 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):

+              dsa_g = keyptr[0:octets]

+              keyptr = keyptr[octets:]

+              dsa_y = keyptr[0:octets]

+ -            pubkey = CryptoDSA.construct(

+ -                (number.bytes_to_long(dsa_y),

+ -                 number.bytes_to_long(dsa_g),

+ -                 number.bytes_to_long(dsa_p),

+ -                 number.bytes_to_long(dsa_q)))

+ -            sig = rrsig.signature[1:]

+ +            try:

+ +                public_key = dsa.DSAPublicNumbers(

+ +                    _bytes_to_long(dsa_y),

+ +                    dsa.DSAParameterNumbers(

+ +                        _bytes_to_long(dsa_p),

+ +                        _bytes_to_long(dsa_q),

+ +                        _bytes_to_long(dsa_g))).public_key(default_backend())

+ +            except ValueError:

+ +                raise ValidationFailure('invalid public key')

+ +            sig_r = rrsig.signature[1:21]

+ +            sig_s = rrsig.signature[21:]

+ +            sig = utils.encode_dss_signature(_bytes_to_long(sig_r),

+ +                                             _bytes_to_long(sig_s))

+          elif _is_ecdsa(rrsig.algorithm):

+ -            # use ecdsa for NIST-384p -- not currently supported by pycryptodome

+ -

+              keyptr = candidate_key.key

+ -

+              if rrsig.algorithm == ECDSAP256SHA256:

+ -                curve = ecdsa.curves.NIST256p

+ -                key_len = 32

+ +                curve = ec.SECP256R1()

+ +                octets = 32

+              elif rrsig.algorithm == ECDSAP384SHA384:

+ -                curve = ecdsa.curves.NIST384p

+ -                key_len = 48

+ -

+ -            x = number.bytes_to_long(keyptr[0:key_len])

+ -            y = number.bytes_to_long(keyptr[key_len:key_len * 2])

+ -            if not ecdsa.ecdsa.point_is_valid(curve.generator, x, y):

+ -                raise ValidationFailure('invalid ECDSA key')

+ -            point = ecdsa.ellipticcurve.Point(curve.curve, x, y, curve.order)

+ -            verifying_key = ecdsa.keys.VerifyingKey.from_public_point(point,

+ -                                                                      curve)

+ -            pubkey = ECKeyWrapper(verifying_key, key_len)

+ -            r = rrsig.signature[:key_len]

+ -            s = rrsig.signature[key_len:]

+ -            sig = ecdsa.ecdsa.Signature(number.bytes_to_long(r),

+ -                                        number.bytes_to_long(s))

+ +                curve = ec.SECP384R1()

+ +                octets = 48

+ +            ecdsa_x = keyptr[0:octets]

+ +            ecdsa_y = keyptr[octets:octets * 2]

+ +            try:

+ +                public_key = ec.EllipticCurvePublicNumbers(

+ +                    curve=curve,

+ +                    x=_bytes_to_long(ecdsa_x),

+ +                    y=_bytes_to_long(ecdsa_y)).public_key(default_backend())

+ +            except ValueError:

+ +                raise ValidationFailure('invalid public key')

+ +            sig_r = rrsig.signature[0:octets]

+ +            sig_s = rrsig.signature[octets:]

+ +            sig = utils.encode_dss_signature(_bytes_to_long(sig_r),

+ +                                             _bytes_to_long(sig_s))

+ 

+          else:

+              raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)

+ 

+ -        hash.update(_to_rdata(rrsig, origin)[:18])

+ -        hash.update(rrsig.signer.to_digestable(origin))

+ +        data = b''

+ +        data += _to_rdata(rrsig, origin)[:18]

+ +        data += rrsig.signer.to_digestable(origin)

+ 

+          if rrsig.labels < len(rrname) - 1:

+              suffix = rrname.split(rrsig.labels + 1)[1]

+ @@ -391,25 +403,21 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):

+                                rrsig.original_ttl)

+          rrlist = sorted(rdataset)

+          for rr in rrlist:

+ -            hash.update(rrnamebuf)

+ -            hash.update(rrfixed)

+ +            data += rrnamebuf

+ +            data += rrfixed

+              rrdata = rr.to_digestable(origin)

+              rrlen = struct.pack('!H', len(rrdata))

+ -            hash.update(rrlen)

+ -            hash.update(rrdata)

+ +            data += rrlen

+ +            data += rrdata

+ 

+ +        chosen_hash = _make_hash(rrsig.algorithm)

+          try:

+              if _is_rsa(rrsig.algorithm):

+ -                verifier = pkcs1_15.new(pubkey)

+ -                # will raise ValueError if verify fails:

+ -                verifier.verify(hash, sig)

+ +                public_key.verify(sig, data, padding.PKCS1v15(), chosen_hash)

+              elif _is_dsa(rrsig.algorithm):

+ -                verifier = DSS.new(pubkey, 'fips-186-3')

+ -                verifier.verify(hash, sig)

+ +                public_key.verify(sig, data, chosen_hash)

+              elif _is_ecdsa(rrsig.algorithm):

+ -                digest = hash.digest()

+ -                if not pubkey.verify(digest, sig):

+ -                    raise ValueError

+ +                public_key.verify(sig, data, ec.ECDSA(chosen_hash))

+              else:

+                  # Raise here for code clarity; this won't actually ever happen

+                  # since if the algorithm is really unknown we'd already have

+ @@ -417,7 +425,7 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):

+                  raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)

+              # If we got here, we successfully verified so we can return without error

+              return

+ -        except ValueError:

+ +        except InvalidSignature:

+              # this happens on an individual validation failure

+              continue

+      # nothing verified -- raise failure:

+ @@ -472,48 +480,24 @@ def _validate(rrset, rrsigset, keys, origin=None, now=None):

+      raise ValidationFailure("no RRSIGs validated")

+ 

+ 

+ -def _need_pycrypto(*args, **kwargs):

+ -    raise NotImplementedError("DNSSEC validation requires pycryptodome/pycryptodomex")

+ +def _need_pyca(*args, **kwargs):

+ +    raise NotImplementedError("DNSSEC validation requires python cryptography")

+ 

+ 

+  try:

+ -    try:

+ -        # test we're using pycryptodome, not pycrypto (which misses SHA1 for example)

+ -        from Crypto.Hash import MD5, SHA1, SHA256, SHA384, SHA512

+ -        from Crypto.PublicKey import RSA as CryptoRSA, DSA as CryptoDSA

+ -        from Crypto.Signature import pkcs1_15, DSS

+ -        from Crypto.Util import number

+ -    except ImportError:

+ -        from Cryptodome.Hash import MD5, SHA1, SHA256, SHA384, SHA512

+ -        from Cryptodome.PublicKey import RSA as CryptoRSA, DSA as CryptoDSA

+ -        from Cryptodome.Signature import pkcs1_15, DSS

+ -        from Cryptodome.Util import number

+ +    from cryptography.exceptions import InvalidSignature

+ +    from cryptography.hazmat.backends import default_backend

+ +    from cryptography.hazmat.primitives import hashes

+ +    from cryptography.hazmat.primitives.asymmetric import padding

+ +    from cryptography.hazmat.primitives.asymmetric import utils

+ +    from cryptography.hazmat.primitives.asymmetric import dsa

+ +    from cryptography.hazmat.primitives.asymmetric import ec

+ +    from cryptography.hazmat.primitives.asymmetric import rsa

+  except ImportError:

+ -    validate = _need_pycrypto

+ -    validate_rrsig = _need_pycrypto

+ -    _have_pycrypto = False

+ -    _have_ecdsa = False

+ +    validate = _need_pyca

+ +    validate_rrsig = _need_pyca

+ +    _have_pyca = False

+  else:

+      validate = _validate

+      validate_rrsig = _validate_rrsig

+ -    _have_pycrypto = True

+ -

+ -    try:

+ -        import ecdsa

+ -        import ecdsa.ecdsa

+ -        import ecdsa.ellipticcurve

+ -        import ecdsa.keys

+ -    except ImportError:

+ -        _have_ecdsa = False

+ -    else:

+ -        _have_ecdsa = True

+ -

+ -        class ECKeyWrapper(object):

+ -

+ -            def __init__(self, key, key_len):

+ -                self.key = key

+ -                self.key_len = key_len

+ -

+ -            def verify(self, digest, sig):

+ -                diglong = number.bytes_to_long(digest)

+ -                return self.key.pubkey.verifies(diglong, sig)

+ +    _have_pyca = True

+ diff --git a/setup.py b/setup.py

+ index 743d43c..2ee38a7 100755

+ --- a/setup.py

+ +++ b/setup.py

+ @@ -75,7 +75,7 @@ direct manipulation of DNS zones, messages, names, and records.""",

+      'provides': ['dns'],

+      'extras_require': {

+          'IDNA': ['idna>=2.1'],

+ -        'DNSSEC': ['pycryptodome', 'ecdsa>=0.13'],

+ +        'DNSSEC': ['cryptography>=2.3'],

+          },

+      'ext_modules': ext_modules if compile_cython else None,

+      'zip_safe': False if compile_cython else None,

+ diff --git a/tests/test_dnssec.py b/tests/test_dnssec.py

+ index c87862a..20b52b2 100644

+ --- a/tests/test_dnssec.py

+ +++ b/tests/test_dnssec.py

+ @@ -151,8 +151,8 @@ abs_ecdsa384_soa_rrsig = dns.rrset.from_text('example.', 86400, 'IN', 'RRSIG',

+ 

+ 

+ 

+ -@unittest.skipUnless(dns.dnssec._have_pycrypto,

+ -                     "Pycryptodome cannot be imported")

+ +@unittest.skipUnless(dns.dnssec._have_pyca,

+ +                     "Python Cryptography cannot be imported")

+  class DNSSECValidatorTestCase(unittest.TestCase):

+ 

+      def testAbsoluteRSAGood(self): # type: () -> None

+ @@ -199,28 +199,20 @@ class DNSSECValidatorTestCase(unittest.TestCase):

+          ds = dns.dnssec.make_ds(abs_example, example_sep_key, 'SHA256')

+          self.failUnless(ds == example_ds_sha256)

+ 

+ -    @unittest.skipUnless(dns.dnssec._have_ecdsa,

+ -                         "python ECDSA cannot be imported")

+      def testAbsoluteECDSA256Good(self): # type: () -> None

+          dns.dnssec.validate(abs_ecdsa256_soa, abs_ecdsa256_soa_rrsig,

+                              abs_ecdsa256_keys, None, when3)

+ 

+ -    @unittest.skipUnless(dns.dnssec._have_ecdsa,

+ -                         "python ECDSA cannot be imported")

+      def testAbsoluteECDSA256Bad(self): # type: () -> None

+          def bad(): # type: () -> None

+              dns.dnssec.validate(abs_other_ecdsa256_soa, abs_ecdsa256_soa_rrsig,

+                                  abs_ecdsa256_keys, None, when3)

+          self.failUnlessRaises(dns.dnssec.ValidationFailure, bad)

+ 

+ -    @unittest.skipUnless(dns.dnssec._have_ecdsa,

+ -                         "python ECDSA cannot be imported")

+      def testAbsoluteECDSA384Good(self): # type: () -> None

+          dns.dnssec.validate(abs_ecdsa384_soa, abs_ecdsa384_soa_rrsig,

+                              abs_ecdsa384_keys, None, when4)

+ 

+ -    @unittest.skipUnless(dns.dnssec._have_ecdsa,

+ -                         "python ECDSA cannot be imported")

+      def testAbsoluteECDSA384Bad(self): # type: () -> None

+          def bad(): # type: () -> None

+              dns.dnssec.validate(abs_other_ecdsa384_soa, abs_ecdsa384_soa_rrsig,

+ --

+ 2.26.2

+ 

Hello @kevin

I bundled last version of dns1 (1.16.0) with patch submited in [1] as Miro suggested in [2].
Scratch build result: https://koji.fedoraproject.org/koji/taskinfo?taskID=56440651
I installed it in my rawhide environment, it looks fine but better if you could to double check.

[1] https://src.fedoraproject.org/rpms/python-dns/c/84c2874c7a39e72e7c074afb8f7682fa46ab2859?branch=master
[2] https://bugzilla.redhat.com/show_bug.cgi?id=1896191#c5

you can probably use %{pypi_source dnspython 1.16.0 zip}

rebased onto f6b6a86

3 years ago

you can probably use %{pypi_source dnspython 1.16.0 zip}

Neat, wasn't aware of the %__pypi_default_extension macro. Thanks

Also < 2 maybe?

Yes, you're right

A nitpick: I suggest always including the source and patch and only unpacking / applying it when the conditional is set. That way, the SRPM can be used to produce both options.

Other than that, this looks good, although I have not checked whether it actually works.

A nitpick: I suggest always including the source and patch and only unpacking / applying it when the conditional is set. That way, the SRPM can be used to produce both options.

I like the idea, I'll keep it (note: that way the %autosetup macro cannot be used)

rebased onto c8aa194

3 years ago

Thanks \o/

%autosetup can be used with -N but that kinda looses the advantage :D

Sorry we have to have this, but looks ok to me... can you merge and build or would you like me to?

On my side, I cannot merge it, nor build it

Pull-Request has been merged by churchyard

3 years ago

This also needs Fedora 33 backport, correct?

The build failed:

======================================================================
ERROR: Failure: ModuleNotFoundError (No module named 'OpenSSL.tsafe')
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/nose/failure.py", line 39, in runTest
    raise self.exc_val.with_traceback(self.tb)
  File "/usr/lib/python3.9/site-packages/nose/loader.py", line 416, in loadTestsFromName
    module = self.importer.importFromPath(
  File "/usr/lib/python3.9/site-packages/nose/importer.py", line 47, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/usr/lib/python3.9/site-packages/nose/importer.py", line 94, in importFromDir
    mod = load_module(part_fqname, fh, filename, desc)
  File "/usr/lib64/python3.9/imp.py", line 244, in load_module
    return load_package(name, filename)
  File "/usr/lib64/python3.9/imp.py", line 216, in load_package
    return _load(spec)
  File "<frozen importlib._bootstrap>", line 711, in _load
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 790, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/builddir/build/BUILD/eventlet-087ba743c7af8a40ac1e4e2ec89409eee3b4233e/eventlet/green/OpenSSL/__init__.py", line 3, in <module>
    from . import tsafe
  File "/builddir/build/BUILD/eventlet-087ba743c7af8a40ac1e4e2ec89409eee3b4233e/eventlet/green/OpenSSL/tsafe.py", line 1, in <module>
    from OpenSSL.tsafe import *
ModuleNotFoundError: No module named 'OpenSSL.tsafe'
======================================================================
ERROR: tests.openssl_test.test_import
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python3.9/site-packages/nose/case.py", line 197, in runTest
    self.test(*self.arg)
  File "/builddir/build/BUILD/eventlet-087ba743c7af8a40ac1e4e2ec89409eee3b4233e/tests/openssl_test.py", line 15, in test_import
    import eventlet.green.OpenSSL.tsafe
  File "/builddir/build/BUILD/eventlet-087ba743c7af8a40ac1e4e2ec89409eee3b4233e/eventlet/green/OpenSSL/__init__.py", line 3, in <module>
    from . import tsafe
  File "/builddir/build/BUILD/eventlet-087ba743c7af8a40ac1e4e2ec89409eee3b4233e/eventlet/green/OpenSSL/tsafe.py", line 1, in <module>
    from OpenSSL.tsafe import *
ModuleNotFoundError: No module named 'OpenSSL.tsafe'
----------------------------------------------------------------------

Fixed when upgrading to 0.30.0. (it seems... please do doublecheck! :)

Fixed when upgrading to 0.30.0. (it seems... please do doublecheck! :)

Well, it built. No idea if it actually works, but the tests passed, so \o/