From 07b056ad2531d01b50df43baa757f61e1cdbbb2f Mon Sep 17 00:00:00 2001
From: Thomas Woerner <twoerner@redhat.com>
Date: Wed, 6 Jul 2022 11:21:08 +0200
Subject: [PATCH] Provide own getargspec for roles and modules with Python 3.11

Python 3.11 dropped compat inspect.getargspec. As the roles and modules
need to support Python2 and Python3, the code for getargspec has been
copied from Python 3.10 and is added as a fallback as soon as getargspec
can not be imported from inspect. The copied getargspec is using
getfullargspec internally.

Fixes: #855 (Python's inspect.getargspec was removed in version 3.11)
---
 .../module_utils/ansible_freeipa_module.py    | 27 +++++++++++++++--
 roles/ipaclient/library/ipaclient_api.py      |  5 ++--
 .../ipaclient/library/ipaclient_setup_nis.py  |  7 ++---
 .../ipaclient/library/ipaclient_setup_nss.py  |  7 ++---
 .../ipaclient/library/ipaclient_setup_ntp.py  |  6 ++--
 roles/ipaclient/library/ipaclient_test.py     |  5 ++--
 .../module_utils/ansible_ipa_client.py        | 29 ++++++++++++++++---
 .../ipareplica_custodia_import_dm_password.py |  6 ++--
 .../ipareplica/library/ipareplica_setup_ds.py |  8 ++---
 .../library/ipareplica_setup_http.py          |  5 ++--
 .../library/ipareplica_setup_krb.py           |  5 ++--
 roles/ipareplica/library/ipareplica_test.py   |  7 ++---
 .../module_utils/ansible_ipa_replica.py       | 24 ++++++++++++++-
 .../ipaserver/library/ipaserver_setup_ntp.py  |  7 ++---
 roles/ipaserver/library/ipaserver_test.py     |  5 ++--
 .../module_utils/ansible_ipa_server.py        | 24 ++++++++++++++-
 16 files changed, 126 insertions(+), 51 deletions(-)

diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py
index 82f48e85..b6ae7a0d 100644
--- a/plugins/module_utils/ansible_freeipa_module.py
+++ b/plugins/module_utils/ansible_freeipa_module.py
@@ -29,7 +29,7 @@ __all__ = ["gssapi", "netaddr", "api", "ipalib_errors", "Env",
            "DEFAULT_CONFIG", "LDAP_GENERALIZED_TIME_FORMAT",
            "kinit_password", "kinit_keytab", "run", "DN", "VERSION",
            "paths", "get_credentials_if_valid", "Encoding",
-           "load_pem_x509_certificate", "DNSName"]
+           "load_pem_x509_certificate", "DNSName", "getargspec"]
 
 import sys
 
@@ -48,7 +48,28 @@ else:
     import gssapi
     from datetime import datetime
     from contextlib import contextmanager
-    import inspect
+
+    # Import getargspec from inspect or provide own getargspec for
+    # Python 2 compatibility with Python 3.11+.
+    try:
+        from inspect import getargspec
+    except ImportError:
+        from collections import namedtuple
+        from inspect import getfullargspec
+
+        # The code is copied from Python 3.10 inspect.py
+        # Authors: Ka-Ping Yee <ping@lfw.org>
+        #          Yury Selivanov <yselivanov@sprymix.com>
+        ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults')
+
+        def getargspec(func):
+            args, varargs, varkw, defaults, kwonlyargs, _kwonlydefaults, \
+                ann = getfullargspec(func)
+            if kwonlyargs or ann:
+                raise ValueError(
+                    "Function has keyword-only parameters or annotations"
+                    ", use inspect.signature() API which can support them")
+            return ArgSpec(args, varargs, varkw, defaults)
 
     # ansible-freeipa requires locale to be C, IPA requires utf-8.
     os.environ["LANGUAGE"] = "C"
@@ -1228,7 +1249,7 @@ else:
             elif result_handler is not None:
                 if "errors" not in handlers_user_args:
                     # pylint: disable=deprecated-method
-                    argspec = inspect.getargspec(result_handler)
+                    argspec = getargspec(result_handler)
                     if "errors" in argspec.args:
                         handlers_user_args["errors"] = _errors
 
diff --git a/roles/ipaclient/library/ipaclient_api.py b/roles/ipaclient/library/ipaclient_api.py
index 346c93a5..763be405 100644
--- a/roles/ipaclient/library/ipaclient_api.py
+++ b/roles/ipaclient/library/ipaclient_api.py
@@ -75,7 +75,6 @@ subject_base:
 '''
 
 import os
-import inspect
 
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.ansible_ipa_client import (
@@ -83,7 +82,7 @@ from ansible.module_utils.ansible_ipa_client import (
     paths, x509, NUM_VERSION, serialization, certdb, api,
     delete_persistent_client_session_data, write_tmp_file,
     ipa_generate_password, CalledProcessError, errors, disable_ra, DN,
-    CLIENT_INSTALL_ERROR, logger
+    CLIENT_INSTALL_ERROR, logger, getargspec
 )
 
 
@@ -134,7 +133,7 @@ def main():
         # Add CA certs to a temporary NSS database
         try:
             # pylint: disable=deprecated-method
-            argspec = inspect.getargspec(tmp_db.create_db)
+            argspec = getargspec(tmp_db.create_db)
             # pylint: enable=deprecated-method
             if "password_filename" not in argspec.args:
                 tmp_db.create_db()
diff --git a/roles/ipaclient/library/ipaclient_setup_nis.py b/roles/ipaclient/library/ipaclient_setup_nis.py
index 289719d9..f3a4d7d0 100644
--- a/roles/ipaclient/library/ipaclient_setup_nis.py
+++ b/roles/ipaclient/library/ipaclient_setup_nis.py
@@ -57,11 +57,10 @@ EXAMPLES = '''
 RETURN = '''
 '''
 
-import inspect
-
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.ansible_ipa_client import (
-    setup_logging, options, sysrestore, paths, configure_nisdomain
+    setup_logging, options, sysrestore, paths, configure_nisdomain,
+    getargspec
 )
 
 
@@ -83,7 +82,7 @@ def main():
     statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE)
 
     # pylint: disable=deprecated-method
-    argspec = inspect.getargspec(configure_nisdomain)
+    argspec = getargspec(configure_nisdomain)
     # pylint: enable=deprecated-method
     if "statestore" not in argspec.args:
         # NUM_VERSION < 40500:
diff --git a/roles/ipaclient/library/ipaclient_setup_nss.py b/roles/ipaclient/library/ipaclient_setup_nss.py
index 8d1dccae..d61a65a9 100644
--- a/roles/ipaclient/library/ipaclient_setup_nss.py
+++ b/roles/ipaclient/library/ipaclient_setup_nss.py
@@ -141,7 +141,6 @@ RETURN = '''
 
 import os
 import time
-import inspect
 
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.ansible_ipa_client import (
@@ -151,7 +150,7 @@ from ansible.module_utils.ansible_ipa_client import (
     get_certs_from_ldap, DN, certstore, x509, logger, certdb,
     CalledProcessError, tasks, client_dns, configure_certmonger, services,
     update_ssh_keys, save_state, configure_ldap_conf, configure_nslcd_conf,
-    configure_openldap_conf, hardcode_ldap_server
+    configure_openldap_conf, hardcode_ldap_server, getargspec
 )
 
 
@@ -323,7 +322,7 @@ def main():
             pass
 
         # pylint: disable=deprecated-method
-        argspec_save_state = inspect.getargspec(save_state)
+        argspec_save_state = getargspec(save_state)
 
         # Name Server Caching Daemon. Disable for SSSD, use otherwise
         # (if installed)
@@ -387,7 +386,7 @@ def main():
         if not options.no_ac:
             # Modify nsswitch/pam stack
             # pylint: disable=deprecated-method
-            argspec = inspect.getargspec(tasks.modify_nsswitch_pam_stack)
+            argspec = getargspec(tasks.modify_nsswitch_pam_stack)
             if "sudo" in argspec.args:
                 tasks.modify_nsswitch_pam_stack(
                     sssd=options.sssd,
diff --git a/roles/ipaclient/library/ipaclient_setup_ntp.py b/roles/ipaclient/library/ipaclient_setup_ntp.py
index 237bb073..7f4a0e82 100644
--- a/roles/ipaclient/library/ipaclient_setup_ntp.py
+++ b/roles/ipaclient/library/ipaclient_setup_ntp.py
@@ -66,13 +66,11 @@ EXAMPLES = '''
 RETURN = '''
 '''
 
-import inspect
-
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.ansible_ipa_client import (
     setup_logging,
     options, sysrestore, paths, sync_time, logger, ipadiscovery,
-    timeconf
+    timeconf, getargspec
 )
 
 
@@ -114,7 +112,7 @@ def main():
         if options.conf_ntp:
             # Attempt to configure and sync time with NTP server (chrony).
             # pylint: disable=deprecated-method
-            argspec = inspect.getargspec(sync_time)
+            argspec = getargspec(sync_time)
             # pylint: enable=deprecated-method
             if "options" not in argspec.args:
                 synced_ntp = sync_time(options.ntp_servers, options.ntp_pool,
diff --git a/roles/ipaclient/library/ipaclient_test.py b/roles/ipaclient/library/ipaclient_test.py
index 540daba4..e82d4ed0 100644
--- a/roles/ipaclient/library/ipaclient_test.py
+++ b/roles/ipaclient/library/ipaclient_test.py
@@ -197,7 +197,6 @@ nosssd_files:
 
 import os
 import socket
-import inspect
 
 try:
     from ansible.module_utils.six.moves.configparser import RawConfigParser
@@ -212,7 +211,7 @@ from ansible.module_utils.ansible_ipa_client import (
     CLIENT_INSTALL_ERROR, tasks, check_ldap_conf, timeconf, constants,
     validate_hostname, nssldap_exists, gssapi, remove_file,
     check_ip_addresses, ipadiscovery, print_port_conf_info,
-    IPA_PYTHON_VERSION
+    IPA_PYTHON_VERSION, getargspec
 )
 
 
@@ -344,7 +343,7 @@ def main():
 
         if options.realm_name:
             # pylint: disable=deprecated-method
-            argspec = inspect.getargspec(validate_domain_name)
+            argspec = getargspec(validate_domain_name)
             if "entity" in argspec.args:
                 # NUM_VERSION >= 40690:
                 validate_domain_name(options.realm_name, entity="realm")
diff --git a/roles/ipaclient/module_utils/ansible_ipa_client.py b/roles/ipaclient/module_utils/ansible_ipa_client.py
index 0523ebb2..304db30b 100644
--- a/roles/ipaclient/module_utils/ansible_ipa_client.py
+++ b/roles/ipaclient/module_utils/ansible_ipa_client.py
@@ -46,7 +46,7 @@ __all__ = ["gssapi", "version", "ipadiscovery", "api", "errors", "x509",
            "configure_nslcd_conf", "configure_ssh_config",
            "configure_sshd_config", "configure_automount",
            "configure_firefox", "sync_time", "check_ldap_conf",
-           "sssd_enable_ifp"]
+           "sssd_enable_ifp", "getargspec"]
 
 import sys
 
@@ -110,10 +110,31 @@ else:
         # IPA version >= 4.4
 
         # import sys
-        import inspect
         import gssapi
         import logging
 
+        # Import getargspec from inspect or provide own getargspec for
+        # Python 2 compatibility with Python 3.11+.
+        try:
+            from inspect import getargspec
+        except ImportError:
+            from collections import namedtuple
+            from inspect import getfullargspec
+
+            # The code is copied from Python 3.10 inspect.py
+            # Authors: Ka-Ping Yee <ping@lfw.org>
+            #          Yury Selivanov <yselivanov@sprymix.com>
+            ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults')
+
+            def getargspec(func):
+                args, varargs, varkw, defaults, kwonlyargs, _kwonlydefaults, \
+                    ann = getfullargspec(func)
+                if kwonlyargs or ann:
+                    raise ValueError(
+                        "Function has keyword-only parameters or annotations"
+                        ", use inspect.signature() API which can support them")
+                return ArgSpec(args, varargs, varkw, defaults)
+
         from ipapython import version
         try:
             from ipaclient.install import ipadiscovery
@@ -200,7 +221,7 @@ else:
             sys.path.remove(temp_dir)
 
             # pylint: disable=deprecated-method
-            argspec = inspect.getargspec(
+            argspec = getargspec(
                 ipa_client_install.configure_krb5_conf)
             if argspec.keywords is None:
                 def configure_krb5_conf(
@@ -240,7 +261,7 @@ else:
             create_ipa_nssdb = certdb.create_ipa_nssdb
 
             argspec = \
-                inspect.getargspec(ipa_client_install.configure_nisdomain)
+                getargspec(ipa_client_install.configure_nisdomain)
             if len(argspec.args) == 3:
                 configure_nisdomain = ipa_client_install.configure_nisdomain
             else:
diff --git a/roles/ipareplica/library/ipareplica_custodia_import_dm_password.py b/roles/ipareplica/library/ipareplica_custodia_import_dm_password.py
index d6aa61d9..2a2fe044 100644
--- a/roles/ipareplica/library/ipareplica_custodia_import_dm_password.py
+++ b/roles/ipareplica/library/ipareplica_custodia_import_dm_password.py
@@ -96,13 +96,13 @@ RETURN = '''
 '''
 
 import os
-import inspect
 
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.ansible_ipa_replica import (
     AnsibleModuleLog, setup_logging, installer, DN, paths,
     gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
-    gen_ReplicaConfig, gen_remote_api, redirect_stdout, custodiainstance
+    gen_ReplicaConfig, gen_remote_api, redirect_stdout, custodiainstance,
+    getargspec
 )
 
 
@@ -200,7 +200,7 @@ def main():
         ansible_log.debug("-- CUSTODIA IMPORT DM PASSWORD --")
 
         # pylint: disable=deprecated-method
-        argspec = inspect.getargspec(custodia.import_dm_password)
+        argspec = getargspec(custodia.import_dm_password)
         # pylint: enable=deprecated-method
         if "master_host_name" in argspec.args:
             custodia.import_dm_password(config.master_host_name)
diff --git a/roles/ipareplica/library/ipareplica_setup_ds.py b/roles/ipareplica/library/ipareplica_setup_ds.py
index 91d30751..c3c4c864 100644
--- a/roles/ipareplica/library/ipareplica_setup_ds.py
+++ b/roles/ipareplica/library/ipareplica_setup_ds.py
@@ -149,7 +149,6 @@ RETURN = '''
 '''
 
 import os
-import inspect
 
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.ansible_ipa_replica import (
@@ -157,7 +156,8 @@ from ansible.module_utils.ansible_ipa_replica import (
     ansible_module_get_parsed_ip_addresses,
     gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
     gen_ReplicaConfig, gen_remote_api, redirect_stdout, ipaldap,
-    install_replica_ds, install_dns_records, ntpinstance, ScriptError
+    install_replica_ds, install_dns_records, ntpinstance, ScriptError,
+    getargspec
 )
 
 
@@ -317,7 +317,7 @@ def main():
         # Configure dirsrv
         with redirect_stdout(ansible_log):
             # pylint: disable=deprecated-method
-            argspec = inspect.getargspec(install_replica_ds)
+            argspec = getargspec(install_replica_ds)
             # pylint: enable=deprecated-method
             if "promote" in argspec.args:
                 ds = install_replica_ds(config, options, ca_enabled,
@@ -343,7 +343,7 @@ def main():
         # pylint: enable=deprecated-method
         # Always try to install DNS records
         # pylint: disable=deprecated-method
-        argspec = inspect.getargspec(install_dns_records)
+        argspec = getargspec(install_dns_records)
         # pylint: enable=deprecated-method
         if "fstore" not in argspec.args:
             install_dns_records(config, options, remote_api)
diff --git a/roles/ipareplica/library/ipareplica_setup_http.py b/roles/ipareplica/library/ipareplica_setup_http.py
index 015dafd2..2d26d18c 100644
--- a/roles/ipareplica/library/ipareplica_setup_http.py
+++ b/roles/ipareplica/library/ipareplica_setup_http.py
@@ -90,14 +90,13 @@ RETURN = '''
 '''
 
 import os
-import inspect
 
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.ansible_ipa_replica import (
     AnsibleModuleLog, setup_logging, installer, DN, paths, sysrestore,
     gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
     gen_ReplicaConfig, gen_remote_api, api, redirect_stdout, create_ipa_conf,
-    install_http
+    install_http, getargspec
 )
 
 
@@ -203,7 +202,7 @@ def main():
                         master=config.master_host_name)
 
         # pylint: disable=deprecated-method
-        argspec = inspect.getargspec(install_http)
+        argspec = getargspec(install_http)
         # pylint: enable=deprecated-method
         if "promote" in argspec.args:
             install_http(
diff --git a/roles/ipareplica/library/ipareplica_setup_krb.py b/roles/ipareplica/library/ipareplica_setup_krb.py
index cb835959..423f4dec 100644
--- a/roles/ipareplica/library/ipareplica_setup_krb.py
+++ b/roles/ipareplica/library/ipareplica_setup_krb.py
@@ -78,13 +78,12 @@ RETURN = '''
 '''
 
 import os
-import inspect
 
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.ansible_ipa_replica import (
     AnsibleModuleLog, setup_logging, installer, DN, paths, sysrestore,
     gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
-    gen_ReplicaConfig, api, redirect_stdout, install_krb
+    gen_ReplicaConfig, api, redirect_stdout, install_krb, getargspec
 )
 
 
@@ -162,7 +161,7 @@ def main():
 
     with redirect_stdout(ansible_log):
         # pylint: disable=deprecated-method
-        argspec = inspect.getargspec(install_krb)
+        argspec = getargspec(install_krb)
         # pylint: enable=deprecated-method
         if "promote" in argspec.args:
             install_krb(
diff --git a/roles/ipareplica/library/ipareplica_test.py b/roles/ipareplica/library/ipareplica_test.py
index 29b3af7f..7ec6fb16 100644
--- a/roles/ipareplica/library/ipareplica_test.py
+++ b/roles/ipareplica/library/ipareplica_test.py
@@ -136,7 +136,6 @@ RETURN = '''
 '''
 
 import os
-import inspect
 
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.ansible_ipa_replica import (
@@ -144,7 +143,7 @@ from ansible.module_utils.ansible_ipa_replica import (
     ansible_module_get_parsed_ip_addresses, service,
     redirect_stdout, create_ipa_conf, ipautil,
     x509, validate_domain_name, common_check,
-    IPA_PYTHON_VERSION
+    IPA_PYTHON_VERSION, getargspec
 )
 
 
@@ -287,7 +286,7 @@ def main():
     # create_ipa_conf has the additional master argument.
     change_master_for_certmonger = False
     # pylint: disable=deprecated-method
-    argspec = inspect.getargspec(create_ipa_conf)
+    argspec = getargspec(create_ipa_conf)
     # pylint: enable=deprecated-method
     if "master" in argspec.args:
         change_master_for_certmonger = True
@@ -421,7 +420,7 @@ def main():
     try:
         with redirect_stdout(ansible_log):
             # pylint: disable=deprecated-method
-            argspec = inspect.getargspec(common_check)
+            argspec = getargspec(common_check)
             # pylint: enable=deprecated-method
             if "skip_mem_check" in argspec.args:
                 common_check(options.no_ntp, options.skip_mem_check,
diff --git a/roles/ipareplica/module_utils/ansible_ipa_replica.py b/roles/ipareplica/module_utils/ansible_ipa_replica.py
index 10624b1f..5b687231 100644
--- a/roles/ipareplica/module_utils/ansible_ipa_replica.py
+++ b/roles/ipareplica/module_utils/ansible_ipa_replica.py
@@ -46,7 +46,7 @@ __all__ = ["contextlib", "dnsexception", "dnsresolver", "dnsreversename",
            "common_check", "current_domain_level",
            "check_domain_level_is_supported", "promotion_check_ipa_domain",
            "SSSDConfig", "CalledProcessError", "timeconf", "ntpinstance",
-           "dnsname", "kernel_keyring", "krbinstance"]
+           "dnsname", "kernel_keyring", "krbinstance", "getargspec"]
 
 import sys
 
@@ -59,6 +59,28 @@ else:
     import logging
     from contextlib import contextmanager as contextlib_contextmanager
 
+    # Import getargspec from inspect or provide own getargspec for
+    # Python 2 compatibility with Python 3.11+.
+    try:
+        from inspect import getargspec
+    except ImportError:
+        from collections import namedtuple
+        from inspect import getfullargspec
+
+        # The code is copied from Python 3.10 inspect.py
+        # Authors: Ka-Ping Yee <ping@lfw.org>
+        #          Yury Selivanov <yselivanov@sprymix.com>
+        ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults')
+
+        def getargspec(func):
+            args, varargs, varkw, defaults, kwonlyargs, _kwonlydefaults, \
+                ann = getfullargspec(func)
+            if kwonlyargs or ann:
+                raise ValueError(
+                    "Function has keyword-only parameters or annotations"
+                    ", use inspect.signature() API which can support them")
+            return ArgSpec(args, varargs, varkw, defaults)
+
     from ipapython.version import NUM_VERSION, VERSION
 
     if NUM_VERSION < 30201:
diff --git a/roles/ipaserver/library/ipaserver_setup_ntp.py b/roles/ipaserver/library/ipaserver_setup_ntp.py
index 72389053..c2292ebe 100644
--- a/roles/ipaserver/library/ipaserver_setup_ntp.py
+++ b/roles/ipaserver/library/ipaserver_setup_ntp.py
@@ -53,12 +53,11 @@ EXAMPLES = '''
 RETURN = '''
 '''
 
-import inspect
-
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.ansible_ipa_server import (
     AnsibleModuleLog, setup_logging, options, sysrestore, paths,
-    redirect_stdout, time_service, sync_time, ntpinstance, timeconf
+    redirect_stdout, time_service, sync_time, ntpinstance, timeconf,
+    getargspec
 )
 
 
@@ -94,7 +93,7 @@ def main():
         ansible_module.log("Synchronizing time")
 
         # pylint: disable=deprecated-method
-        argspec = inspect.getargspec(sync_time)
+        argspec = getargspec(sync_time)
         # pylint: enable=deprecated-method
         if "options" not in argspec.args:
             synced_ntp = sync_time(options.ntp_servers, options.ntp_pool,
diff --git a/roles/ipaserver/library/ipaserver_test.py b/roles/ipaserver/library/ipaserver_test.py
index 76173b02..d619add0 100644
--- a/roles/ipaserver/library/ipaserver_test.py
+++ b/roles/ipaserver/library/ipaserver_test.py
@@ -212,7 +212,6 @@ RETURN = '''
 
 import os
 import sys
-import inspect
 import random
 from shutil import copyfile
 
@@ -226,7 +225,7 @@ from ansible.module_utils.ansible_ipa_server import (
     read_cache, ca, tasks, check_ldap_conf, timeconf, httpinstance,
     check_dirsrv, ScriptError, get_fqdn, verify_fqdn, BadHostError,
     validate_domain_name, load_pkcs12, IPA_PYTHON_VERSION,
-    encode_certificate, check_available_memory
+    encode_certificate, check_available_memory, getargspec
 )
 from ansible.module_utils import six
 
@@ -944,7 +943,7 @@ def main():
         realm_name = options.realm_name.upper()
 
     # pylint: disable=deprecated-method
-    argspec = inspect.getargspec(validate_domain_name)
+    argspec = getargspec(validate_domain_name)
     # pylint: enable=deprecated-method
     if "entity" in argspec.args:
         # NUM_VERSION >= 40690:
diff --git a/roles/ipaserver/module_utils/ansible_ipa_server.py b/roles/ipaserver/module_utils/ansible_ipa_server.py
index 36ee5d6b..aba6b68a 100644
--- a/roles/ipaserver/module_utils/ansible_ipa_server.py
+++ b/roles/ipaserver/module_utils/ansible_ipa_server.py
@@ -41,7 +41,7 @@ __all__ = ["IPAChangeConf", "certmonger", "sysrestore", "root_logger",
            "adtrustinstance", "IPAAPI_USER", "sync_time", "PKIIniLoader",
            "default_subject_base", "default_ca_subject_dn",
            "check_ldap_conf", "encode_certificate", "decode_certificate",
-           "check_available_memory"]
+           "check_available_memory", "getargspec"]
 
 import sys
 
@@ -58,6 +58,28 @@ else:
     from ansible.module_utils import six
     import base64
 
+    # Import getargspec from inspect or provide own getargspec for
+    # Python 2 compatibility with Python 3.11+.
+    try:
+        from inspect import getargspec
+    except ImportError:
+        from collections import namedtuple
+        from inspect import getfullargspec
+
+        # The code is copied from Python 3.10 inspect.py
+        # Authors: Ka-Ping Yee <ping@lfw.org>
+        #          Yury Selivanov <yselivanov@sprymix.com>
+        ArgSpec = namedtuple('ArgSpec', 'args varargs keywords defaults')
+
+        def getargspec(func):
+            args, varargs, varkw, defaults, kwonlyargs, _kwonlydefaults, \
+                ann = getfullargspec(func)
+            if kwonlyargs or ann:
+                raise ValueError(
+                    "Function has keyword-only parameters or annotations"
+                    ", use inspect.signature() API which can support them")
+            return ArgSpec(args, varargs, varkw, defaults)
+
     from ipapython.version import NUM_VERSION, VERSION
 
     if NUM_VERSION < 30201:
-- 
GitLab