Remove Python 2 specific code (#877)

* Get rid of Python 2 special handling.

* Get rid of more Python 2 specific handling.

* Stop using six.

* ipaddress is part of the standard library since Python 3.

* Add changelog.

* Fix import.

* Remove unneeded imports.
This commit is contained in:
Felix Fontein
2025-05-01 16:21:13 +02:00
committed by GitHub
parent 641e63b08c
commit 65872e884f
29 changed files with 269 additions and 565 deletions

View File

@@ -10,11 +10,9 @@ import datetime
import json
import locale
import time
import traceback
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.common.text.converters import to_bytes
from ansible.module_utils.six import PY3
from ansible.module_utils.urls import fetch_url
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryptography import (
CRYPTOGRAPHY_ERROR,
@@ -43,16 +41,6 @@ from ansible_collections.community.crypto.plugins.module_utils.argspec import (
)
try:
import ipaddress # noqa: F401, pylint: disable=unused-import
except ImportError:
HAS_IPADDRESS = False
IPADDRESS_IMPORT_ERROR = traceback.format_exc()
else:
HAS_IPADDRESS = True
IPADDRESS_IMPORT_ERROR = None
# -1 usually means connection problems
RETRY_STATUS_CODES = (-1, 408, 429, 503)
@@ -345,7 +333,7 @@ class ACMEClient:
try:
# In Python 2, reading from a closed response yields a TypeError.
# In Python 3, read() simply returns ''
if PY3 and resp.closed:
if resp.closed:
raise TypeError
content = resp.read()
except (AttributeError, TypeError):
@@ -440,7 +428,7 @@ class ACMEClient:
try:
# In Python 2, reading from a closed response yields a TypeError.
# In Python 3, read() simply returns ''
if PY3 and resp.closed:
if resp.closed:
raise TypeError
content = resp.read()
except (AttributeError, TypeError):
@@ -557,11 +545,6 @@ def create_default_argspec(
def create_backend(module, needs_acme_v2=True):
if not HAS_IPADDRESS:
module.fail_json(
msg=missing_required_lib("ipaddress"), exception=IPADDRESS_IMPORT_ERROR
)
backend = module.params["select_crypto_backend"]
# Backend autodetect

View File

@@ -8,6 +8,7 @@ from __future__ import annotations
import base64
import binascii
import datetime
import ipaddress
import os
import re
import tempfile
@@ -33,12 +34,6 @@ from ansible_collections.community.crypto.plugins.module_utils.time import (
)
try:
import ipaddress
except ImportError:
pass
_OPENSSL_ENVIRONMENT_UPDATE = dict(LANG="C", LC_ALL="C", LC_MESSAGES="C", LC_CTYPE="C")

View File

@@ -74,7 +74,6 @@ def _parse_acme_timestamp(timestamp_str, with_timezone):
"%Y-%m-%dT%H:%M:%S%z",
"%Y-%m-%dT%H:%M:%S.%f%z",
):
# Note that %z will not work with Python 2... https://stackoverflow.com/a/27829491
try:
result = datetime.datetime.strptime(timestamp_str, format)
except ValueError:

View File

@@ -7,6 +7,7 @@ from __future__ import annotations
import base64
import hashlib
import ipaddress
import json
import re
import time
@@ -22,12 +23,6 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import
)
try:
import ipaddress
except ImportError:
pass
def create_key_authorization(client, token):
"""
Returns the key authorization for the given token

View File

@@ -5,9 +5,9 @@
from __future__ import annotations
from http.client import responses as http_responses
from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.six import PY3, binary_type
from ansible.module_utils.six.moves.http_client import responses as http_responses
def format_http_status(status_code):
@@ -67,7 +67,7 @@ class ACMEProtocolException(ModuleFailException):
try:
# In Python 2, reading from a closed response yields a TypeError.
# In Python 3, read() simply returns ''
if PY3 and response.closed:
if response.closed:
raise TypeError
content = response.read()
except (AttributeError, TypeError):
@@ -75,7 +75,7 @@ class ACMEProtocolException(ModuleFailException):
# Make sure that content_json is None or a dictionary
if content_json is not None and not isinstance(content_json, dict):
if content is None and isinstance(content_json, binary_type):
if content is None and isinstance(content_json, bytes):
content = content_json
content_json = None

View File

@@ -10,9 +10,9 @@ import datetime
import re
import textwrap
import traceback
from urllib.parse import unquote
from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.six.moves.urllib.parse import unquote
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
ModuleFailException,
)

View File

@@ -6,16 +6,16 @@ from __future__ import annotations
import base64
import binascii
import ipaddress
import re
import sys
import traceback
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible.module_utils.six.moves.urllib.parse import (
from urllib.parse import (
ParseResult,
urlparse,
urlunparse,
)
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible_collections.community.crypto.plugins.module_utils.version import (
LooseVersion,
)
@@ -24,8 +24,6 @@ from ._asn1 import serialize_asn1_string_as_der
try:
import ipaddress
import cryptography
from cryptography import x509
from cryptography.exceptions import InvalidSignature
@@ -286,12 +284,8 @@ DN_COMPONENT_START_RE = re.compile(b"^ *([a-zA-z0-9.]+) *= *")
DN_HEX_LETTER = b"0123456789abcdef"
if sys.version_info[0] < 3:
_int_to_byte = chr
else:
def _int_to_byte(value):
return bytes((value,))
def _int_to_byte(value):
return bytes((value,))
def _parse_dn_component(name, sep=b",", decode_remainder=True):

View File

@@ -4,8 +4,6 @@
from __future__ import annotations
import sys
def binary_exp_mod(f, e, m):
"""Computes f^e mod m in O(log e) multiplications modulo m."""
@@ -99,82 +97,36 @@ def quick_is_not_prime(n):
return False
python_version = (sys.version_info[0], sys.version_info[1])
if python_version >= (2, 7) or python_version >= (3, 1):
# Ansible still supports Python 2.6 on remote nodes
def count_bytes(no):
"""
Given an integer, compute the number of bytes necessary to store its absolute value.
"""
no = abs(no)
if no == 0:
return 0
return (no.bit_length() + 7) // 8
def count_bits(no):
"""
Given an integer, compute the number of bits necessary to store its absolute value.
"""
no = abs(no)
if no == 0:
return 0
return no.bit_length()
else:
# Slow, but works
def count_bytes(no):
"""
Given an integer, compute the number of bytes necessary to store its absolute value.
"""
no = abs(no)
count = 0
while no > 0:
no >>= 8
count += 1
return count
def count_bits(no):
"""
Given an integer, compute the number of bits necessary to store its absolute value.
"""
no = abs(no)
count = 0
while no > 0:
no >>= 1
count += 1
return count
def count_bytes(no):
"""
Given an integer, compute the number of bytes necessary to store its absolute value.
"""
no = abs(no)
if no == 0:
return 0
return (no.bit_length() + 7) // 8
if sys.version_info[0] >= 3:
# Python 3 (and newer)
def _convert_int_to_bytes(count, no):
return no.to_bytes(count, byteorder="big")
def count_bits(no):
"""
Given an integer, compute the number of bits necessary to store its absolute value.
"""
no = abs(no)
if no == 0:
return 0
return no.bit_length()
def _convert_bytes_to_int(data):
return int.from_bytes(data, byteorder="big", signed=False)
def _to_hex(no):
return hex(no)[2:]
def _convert_int_to_bytes(count, no):
return no.to_bytes(count, byteorder="big")
else:
# Python 2
def _convert_int_to_bytes(count, n):
if n == 0 and count == 0:
return ""
h = f"{n:x}"
if len(h) > 2 * count:
raise Exception(f"Number {n} needs more than {count} bytes!")
return ("0" * (2 * count - len(h)) + h).decode("hex")
def _convert_bytes_to_int(data):
v = 0
for x in data:
v = (v << 8) | ord(x)
return v
def _convert_bytes_to_int(data):
return int.from_bytes(data, byteorder="big", signed=False)
def _to_hex(no):
return f"{no:x}"
def _to_hex(no):
return f"{no:x}"
def convert_int_to_bytes(no, count=None):

View File

@@ -14,11 +14,11 @@ import json
import os
import re
import traceback
from urllib.error import HTTPError
from urllib.parse import urlencode
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.common.text.converters import to_native, to_text
from ansible.module_utils.six.moves.urllib.error import HTTPError
from ansible.module_utils.six.moves.urllib.parse import urlencode
from ansible.module_utils.urls import Request

View File

@@ -8,7 +8,6 @@ import abc
import binascii
import datetime as _datetime
import os
import sys
from base64 import b64encode
from datetime import datetime
from hashlib import sha256
@@ -68,13 +67,8 @@ _ECDSA_CURVE_IDENTIFIERS_LOOKUP = {
b"nistp521": "ecdsa-nistp521",
}
_USE_TIMEZONE = sys.version_info >= (3, 6)
_ALWAYS = _add_or_remove_timezone(datetime(1970, 1, 1), with_timezone=_USE_TIMEZONE)
_FOREVER = (
datetime(9999, 12, 31, 23, 59, 59, 999999, _UTC) if _USE_TIMEZONE else datetime.max
)
_ALWAYS = _add_or_remove_timezone(datetime(1970, 1, 1), with_timezone=True)
_FOREVER = datetime(9999, 12, 31, 23, 59, 59, 999999, _UTC)
_CRITICAL_OPTIONS = (
"force-command",
@@ -99,9 +93,6 @@ _EXTENSIONS = (
"permit-user-rc",
)
if six.PY3:
long = int
class OpensshCertificateTimeParameters:
def __init__(self, valid_from, valid_to):
@@ -172,13 +163,13 @@ class OpensshCertificateTimeParameters:
result = OpensshCertificateTimeParameters._time_string_to_datetime(
time_string_or_timestamp.strip()
)
elif isinstance(time_string_or_timestamp, (long, int)):
elif isinstance(time_string_or_timestamp, int):
result = OpensshCertificateTimeParameters._timestamp_to_datetime(
time_string_or_timestamp
)
else:
raise ValueError(
f"Value must be of type (str, unicode, int, long) not {type(time_string_or_timestamp)}"
f"Value must be of type (str, unicode, int) not {type(time_string_or_timestamp)}"
)
except ValueError:
raise
@@ -192,12 +183,7 @@ class OpensshCertificateTimeParameters:
result = _FOREVER
else:
try:
if _USE_TIMEZONE:
result = datetime.fromtimestamp(
timestamp, tz=_datetime.timezone.utc
)
else:
result = datetime.utcfromtimestamp(timestamp)
result = datetime.fromtimestamp(timestamp, tz=_datetime.timezone.utc)
except OverflowError:
raise ValueError
return result
@@ -210,15 +196,13 @@ class OpensshCertificateTimeParameters:
elif time_string == "forever":
result = _FOREVER
elif is_relative_time_string(time_string):
result = convert_relative_to_datetime(
time_string, with_timezone=_USE_TIMEZONE
)
result = convert_relative_to_datetime(time_string, with_timezone=True)
else:
for time_format in ("%Y-%m-%d", "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"):
try:
result = _add_or_remove_timezone(
datetime.strptime(time_string, time_format),
with_timezone=_USE_TIMEZONE,
with_timezone=True,
)
except ValueError:
pass

View File

@@ -10,8 +10,6 @@ import re
from contextlib import contextmanager
from struct import Struct
from ansible.module_utils.six import PY3
# Protocol References
# -------------------
@@ -25,9 +23,6 @@ from ansible.module_utils.six import PY3
# https://github.com/pyca/cryptography/blob/main/src/cryptography/hazmat/primitives/serialization/ssh.py
# https://github.com/paramiko/paramiko/blob/master/paramiko/message.py
if PY3:
long = int
# 0 (False) or 1 (True) encoded as a single byte
_BOOLEAN = Struct(b"?")
# Unsigned 8-bit integer in network-byte-order
@@ -93,7 +88,7 @@ class OpensshParser:
if not isinstance(data, (bytes, bytearray)):
raise TypeError(f"Data must be bytes-like not {type(data)}")
self._data = memoryview(data) if PY3 else data
self._data = memoryview(data)
self._pos = 0
def boolean(self):
@@ -125,7 +120,7 @@ class OpensshParser:
value = self._data[self._pos : next_pos]
self._pos = next_pos
# Cast to bytes is required as a memoryview slice is itself a memoryview
return value if not PY3 else bytes(value)
return bytes(value)
def mpint(self):
return self._big_int(self.string(), "big", signed=True)
@@ -223,47 +218,7 @@ class OpensshParser:
f"Byte_order must be one of (big, little) not {byte_order}"
)
if PY3:
return int.from_bytes(raw_string, byte_order, signed=signed)
result = 0
byte_length = len(raw_string)
if byte_length > 0:
# Check sign-bit
msb = raw_string[0] if byte_order == "big" else raw_string[-1]
negative = bool(ord(msb) & 0x80)
# Match pad value for two's complement
pad = b"\xff" if signed and negative else b"\x00"
# The definition of ``mpint`` enforces that unnecessary bytes are not encoded so they are added back
pad_length = 4 - byte_length % 4
if pad_length < 4:
raw_string = (
pad * pad_length + raw_string
if byte_order == "big"
else raw_string + pad * pad_length
)
byte_length += pad_length
# Accumulate arbitrary precision integer bytes in the appropriate order
if byte_order == "big":
for i in range(0, byte_length, cls.UINT32_OFFSET):
left_shift = result << cls.UINT32_OFFSET * 8
result = (
left_shift
+ _UINT32.unpack(raw_string[i : i + cls.UINT32_OFFSET])[0]
)
else:
for i in range(byte_length, 0, -cls.UINT32_OFFSET):
left_shift = result << cls.UINT32_OFFSET * 8
result = (
left_shift
+ _UINT32_LE.unpack(raw_string[i - cls.UINT32_OFFSET : i])[0]
)
# Adjust for two's complement
if signed and negative:
result -= 1 << (8 * byte_length)
return result
return int.from_bytes(raw_string, byte_order, signed=signed)
class _OpensshWriter:
@@ -307,8 +262,8 @@ class _OpensshWriter:
return self
def uint64(self, value):
if not isinstance(value, (long, int)):
raise TypeError(f"Value must be of type (long, int) not {type(value)}")
if not isinstance(value, int):
raise TypeError(f"Value must be of type int not {type(value)}")
if value < 0 or value > _UINT64_MAX:
raise ValueError(
f"Value must be a positive integer less than {_UINT64_MAX}"
@@ -327,8 +282,8 @@ class _OpensshWriter:
return self
def mpint(self, value):
if not isinstance(value, (int, long)):
raise TypeError(f"Value must be of type (long, int) not {type(value)}")
if not isinstance(value, int):
raise TypeError(f"Value must be of type int not {type(value)}")
self.string(self._int_to_mpint(value))
@@ -373,42 +328,12 @@ class _OpensshWriter:
@staticmethod
def _int_to_mpint(num):
if PY3:
byte_length = (num.bit_length() + 7) // 8
try:
result = num.to_bytes(byte_length, "big", signed=True)
# Handles values which require \x00 or \xFF to pad sign-bit
except OverflowError:
result = num.to_bytes(byte_length + 1, "big", signed=True)
else:
result = bytes()
# 0 and -1 are treated as special cases since they are used as sentinels for all other values
if num == 0:
result += b"\x00"
elif num == -1:
result += b"\xff"
elif num > 0:
while num >> 32:
result = _UINT32.pack(num & _UINT32_MAX) + result
num = num >> 32
# Pack last 4 bytes individually to discard insignificant bytes
while num:
result = _UBYTE.pack(num & _UBYTE_MAX) + result
num = num >> 8
# Zero pad final byte if most-significant bit is 1 as per mpint definition
if ord(result[0]) & 0x80:
result = b"\x00" + result
else:
while (num >> 32) < -1:
result = _UINT32.pack(num & _UINT32_MAX) + result
num = num >> 32
while num < -1:
result = _UBYTE.pack(num & _UBYTE_MAX) + result
num = num >> 8
if not ord(result[0]) & 0x80:
result = b"\xff" + result
return result
byte_length = (num.bit_length() + 7) // 8
try:
return num.to_bytes(byte_length, "big", signed=True)
# Handles values which require \x00 or \xFF to pad sign-bit
except OverflowError:
return num.to_bytes(byte_length + 1, "big", signed=True)
def bytes(self):
return bytes(self._buff)

View File

@@ -6,7 +6,6 @@ from __future__ import annotations
import datetime
import re
import sys
from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
@@ -68,29 +67,11 @@ def add_or_remove_timezone(timestamp, with_timezone):
)
if sys.version_info < (3, 3):
def get_epoch_seconds(timestamp):
epoch = datetime.datetime(
1970, 1, 1, tzinfo=UTC if timestamp.tzinfo is not None else None
)
delta = timestamp - epoch
try:
return delta.total_seconds()
except AttributeError:
# Python 2.6 and earlier: total_seconds() does not yet exist, so we use the formula from
# https://docs.python.org/2/library/datetime.html#datetime.timedelta.total_seconds
return (
delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10**6
) / 10**6
else:
def get_epoch_seconds(timestamp):
if timestamp.tzinfo is None:
# timestamp.timestamp() is offset by the local timezone if timestamp has no timezone
timestamp = ensure_utc_timezone(timestamp)
return timestamp.timestamp()
def get_epoch_seconds(timestamp):
if timestamp.tzinfo is None:
# timestamp.timestamp() is offset by the local timezone if timestamp has no timezone
timestamp = ensure_utc_timezone(timestamp)
return timestamp.timestamp()
def from_epoch_seconds(timestamp, with_timezone):