Add type hints and type checking (#885)

* Enable basic type checking.

* Fix first errors.

* Add changelog fragment.

* Add types to module_utils and plugin_utils (without module backends).

* Add typing hints for acme_* modules.

* Add typing to X.509 certificate modules, and add more helpers.

* Add typing to remaining module backends.

* Add typing for action, filter, and lookup plugins.

* Bump ansible-core 2.19 beta requirement for typing.

* Add more typing definitions.

* Add typing to some unit tests.
This commit is contained in:
Felix Fontein
2025-05-11 18:00:11 +02:00
committed by GitHub
parent 82f0176773
commit f758d94fba
124 changed files with 4986 additions and 2662 deletions

View File

@@ -39,6 +39,8 @@ _value:
type: string
"""
import typing as t
from ansible.errors import AnsibleFilterError
from ansible.module_utils.common.text.converters import to_bytes
from ansible_collections.community.crypto.plugins.module_utils.gnupg.cli import (
@@ -50,7 +52,7 @@ from ansible_collections.community.crypto.plugins.plugin_utils.gnupg import (
)
def gpg_fingerprint(input):
def gpg_fingerprint(input: str | bytes) -> str:
if not isinstance(input, (str, bytes)):
raise AnsibleFilterError(
f"The input for the community.crypto.gpg_fingerprint filter must be a string; got {type(input)} instead"
@@ -65,7 +67,7 @@ def gpg_fingerprint(input):
class FilterModule:
"""Ansible jinja2 filters"""
def filters(self):
def filters(self) -> dict[str, t.Callable]:
return {
"gpg_fingerprint": gpg_fingerprint,
}

View File

@@ -274,6 +274,8 @@ _value:
sample: 12345
"""
import typing as t
from ansible.errors import AnsibleFilterError
from ansible.module_utils.common.text.converters import to_bytes, to_native
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
@@ -287,7 +289,9 @@ from ansible_collections.community.crypto.plugins.plugin_utils.filter_module imp
)
def openssl_csr_info_filter(data, name_encoding="ignore"):
def openssl_csr_info_filter(
data: str | bytes, name_encoding: t.Literal["ignore", "idna", "unicode"] = "ignore"
) -> dict[str, t.Any]:
"""Extract information from X.509 PEM certificate."""
if not isinstance(data, (str, bytes)):
raise AnsibleFilterError(
@@ -313,7 +317,7 @@ def openssl_csr_info_filter(data, name_encoding="ignore"):
class FilterModule:
"""Ansible jinja2 filters"""
def filters(self):
def filters(self) -> dict[str, t.Callable]:
return {
"openssl_csr_info": openssl_csr_info_filter,
}

View File

@@ -146,8 +146,10 @@ _value:
type: dict
"""
import typing as t
from ansible.errors import AnsibleFilterError
from ansible.module_utils.common.text.converters import to_bytes
from ansible.module_utils.common.text.converters import to_bytes, to_text
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
)
@@ -161,8 +163,10 @@ from ansible_collections.community.crypto.plugins.plugin_utils.filter_module imp
def openssl_privatekey_info_filter(
data, passphrase=None, return_private_key_data=False
):
data: str | bytes,
passphrase: str | bytes | None = None,
return_private_key_data: bool = False,
) -> dict[str, t.Any]:
"""Extract information from X.509 PEM certificate."""
if not isinstance(data, (str, bytes)):
raise AnsibleFilterError(
@@ -182,7 +186,7 @@ def openssl_privatekey_info_filter(
result = get_privatekey_info(
module,
content=to_bytes(data),
passphrase=passphrase,
passphrase=to_text(passphrase) if passphrase is not None else None,
return_private_key_data=return_private_key_data,
)
result.pop("can_parse_key", None)
@@ -197,7 +201,7 @@ def openssl_privatekey_info_filter(
class FilterModule:
"""Ansible jinja2 filters"""
def filters(self):
def filters(self) -> dict[str, t.Callable]:
return {
"openssl_privatekey_info": openssl_privatekey_info_filter,
}

View File

@@ -123,6 +123,8 @@ _value:
returned: When RV(_value.type=DSA) or RV(_value.type=ECC)
"""
import typing as t
from ansible.errors import AnsibleFilterError
from ansible.module_utils.common.text.converters import to_bytes
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
@@ -137,7 +139,7 @@ from ansible_collections.community.crypto.plugins.plugin_utils.filter_module imp
)
def openssl_publickey_info_filter(data):
def openssl_publickey_info_filter(data: str | bytes) -> dict[str, t.Any]:
"""Extract information from OpenSSL PEM public key."""
if not isinstance(data, (str, bytes)):
raise AnsibleFilterError(
@@ -156,7 +158,7 @@ def openssl_publickey_info_filter(data):
class FilterModule:
"""Ansible jinja2 filters"""
def filters(self):
def filters(self) -> dict[str, t.Callable]:
return {
"openssl_publickey_info": openssl_publickey_info_filter,
}

View File

@@ -39,6 +39,8 @@ _value:
type: int
"""
import typing as t
from ansible.errors import AnsibleFilterError
from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.crypto.plugins.module_utils.serial import (
@@ -46,7 +48,7 @@ from ansible_collections.community.crypto.plugins.module_utils.serial import (
)
def parse_serial_filter(input):
def parse_serial_filter(input: str | bytes) -> int:
if not isinstance(input, (str, bytes)):
raise AnsibleFilterError(
f"The input for the community.crypto.parse_serial filter must be a string; got {type(input)} instead"
@@ -60,7 +62,7 @@ def parse_serial_filter(input):
class FilterModule:
"""Ansible jinja2 filters"""
def filters(self):
def filters(self) -> dict[str, t.Callable]:
return {
"parse_serial": parse_serial_filter,
}

View File

@@ -38,6 +38,8 @@ _value:
elements: string
"""
import typing as t
from ansible.errors import AnsibleFilterError
from ansible.module_utils.common.text.converters import to_text
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
@@ -45,21 +47,20 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import
)
def split_pem_filter(data):
def split_pem_filter(data: str | bytes) -> list[str]:
"""Split PEM file."""
if not isinstance(data, (str, bytes)):
raise AnsibleFilterError(
f"The community.crypto.split_pem input must be a text type, not {type(data)}"
)
data = to_text(data)
return split_pem_list(data)
return split_pem_list(to_text(data))
class FilterModule:
"""Ansible jinja2 filters"""
def filters(self):
def filters(self) -> dict[str, t.Callable]:
return {
"split_pem": split_pem_filter,
}

View File

@@ -39,11 +39,13 @@ _value:
type: string
"""
import typing as t
from ansible.errors import AnsibleFilterError
from ansible_collections.community.crypto.plugins.module_utils.serial import to_serial
def to_serial_filter(input):
def to_serial_filter(input: int) -> str:
if not isinstance(input, int):
raise AnsibleFilterError(
f"The input for the community.crypto.to_serial filter must be an integer; got {type(input)} instead"
@@ -61,7 +63,7 @@ def to_serial_filter(input):
class FilterModule:
"""Ansible jinja2 filters"""
def filters(self):
def filters(self) -> dict[str, t.Callable]:
return {
"to_serial": to_serial_filter,
}

View File

@@ -308,6 +308,8 @@ _value:
type: str
"""
import typing as t
from ansible.errors import AnsibleFilterError
from ansible.module_utils.common.text.converters import to_bytes, to_native
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
@@ -321,7 +323,9 @@ from ansible_collections.community.crypto.plugins.plugin_utils.filter_module imp
)
def x509_certificate_info_filter(data, name_encoding="ignore"):
def x509_certificate_info_filter(
data: str | bytes, name_encoding: t.Literal["ignore", "idna", "unicode"] = "ignore"
) -> dict[str, t.Any]:
"""Extract information from X.509 PEM certificate."""
if not isinstance(data, (str, bytes)):
raise AnsibleFilterError(
@@ -347,7 +351,7 @@ def x509_certificate_info_filter(data, name_encoding="ignore"):
class FilterModule:
"""Ansible jinja2 filters"""
def filters(self):
def filters(self) -> dict[str, t.Callable]:
return {
"x509_certificate_info": x509_certificate_info_filter,
}

View File

@@ -155,6 +155,7 @@ _value:
import base64
import binascii
import typing as t
from ansible.errors import AnsibleFilterError
from ansible.module_utils.common.text.converters import to_bytes, to_native
@@ -172,7 +173,11 @@ from ansible_collections.community.crypto.plugins.plugin_utils.filter_module imp
)
def x509_crl_info_filter(data, name_encoding="ignore", list_revoked_certificates=True):
def x509_crl_info_filter(
data: str | bytes,
name_encoding: t.Literal["ignore", "idna", "unicode"] = "ignore",
list_revoked_certificates: bool = True,
) -> dict[str, t.Any]:
"""Extract information from X.509 PEM certificate."""
if not isinstance(data, (str, bytes)):
raise AnsibleFilterError(
@@ -192,17 +197,19 @@ def x509_crl_info_filter(data, name_encoding="ignore", list_revoked_certificates
f'The name_encoding option must be one of the values "ignore", "idna", or "unicode", not "{name_encoding}"'
)
data = to_bytes(data)
if not identify_pem_format(data):
data_bytes = to_bytes(data)
if not identify_pem_format(data_bytes):
try:
data = base64.b64decode(to_native(data))
data_bytes = base64.b64decode(to_native(data_bytes))
except (binascii.Error, TypeError, ValueError, UnicodeEncodeError):
pass
module = FilterModuleMock({"name_encoding": name_encoding})
try:
return get_crl_info(
module, content=data, list_revoked_certificates=list_revoked_certificates
module,
content=data_bytes,
list_revoked_certificates=list_revoked_certificates,
)
except OpenSSLObjectError as exc:
raise AnsibleFilterError(str(exc))
@@ -211,7 +218,7 @@ def x509_crl_info_filter(data, name_encoding="ignore", list_revoked_certificates
class FilterModule:
"""Ansible jinja2 filters"""
def filters(self):
def filters(self) -> dict[str, t.Callable]:
return {
"x509_crl_info": x509_crl_info_filter,
}