mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-08-04 05:44:41 +00:00
ansible_freeipa_module: Fix ansible-test fake execution test findings
All imports that are only available after installing IPA need to be in a try exception clause to be able to pass the fake execution test. The old workaround "if 'ansible.executor' in sys.modules:" is not working with this test anymore. If the imports can not be done, all used and needed attributes are defines with the value None. A check has been added to IPAAnsibleModule.__init__ to make sure that it fails if the imports have not been done successfully.
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
# Sergio Oliveira Campos <seocam@redhat.com>
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2019 Red Hat
|
||||
# Copyright (C) 2019-2022 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
@@ -31,29 +31,26 @@ __all__ = ["gssapi", "netaddr", "api", "ipalib_errors", "Env",
|
||||
"paths", "tasks", "get_credentials_if_valid", "Encoding",
|
||||
"load_pem_x509_certificate", "DNSName", "getargspec"]
|
||||
|
||||
import os
|
||||
import sys
|
||||
import operator
|
||||
import tempfile
|
||||
import shutil
|
||||
import socket
|
||||
import base64
|
||||
from datetime import datetime
|
||||
from contextlib import contextmanager
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.common.text.converters import jsonify
|
||||
from ansible.module_utils import six
|
||||
from ansible.module_utils.common._collections_compat import Mapping
|
||||
|
||||
# HACK: workaround for Ansible 2.9
|
||||
# https://github.com/ansible/ansible/issues/68361
|
||||
if 'ansible.executor' in sys.modules:
|
||||
for attr in __all__:
|
||||
setattr(sys.modules[__name__], attr, None)
|
||||
else:
|
||||
import operator
|
||||
import os
|
||||
import uuid
|
||||
import tempfile
|
||||
import shutil
|
||||
import netaddr
|
||||
import gssapi
|
||||
from datetime import datetime
|
||||
from contextlib import contextmanager
|
||||
|
||||
# Import getargspec from inspect or provide own getargspec for
|
||||
# Python 2 compatibility with Python 3.11+.
|
||||
try:
|
||||
# Import getargspec from inspect or provide own getargspec for
|
||||
# Python 2 compatibility with Python 3.11+.
|
||||
try:
|
||||
from inspect import getargspec
|
||||
except ImportError:
|
||||
except ImportError:
|
||||
from collections import namedtuple
|
||||
from inspect import getfullargspec
|
||||
|
||||
@@ -71,8 +68,11 @@ else:
|
||||
", 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"
|
||||
|
||||
try:
|
||||
import uuid
|
||||
import netaddr
|
||||
import gssapi
|
||||
|
||||
from ipalib import api
|
||||
from ipalib import errors as ipalib_errors # noqa
|
||||
@@ -91,9 +91,6 @@ else:
|
||||
from ipalib.krb_utils import get_credentials_if_valid
|
||||
from ipapython.dnsutil import DNSName
|
||||
from ipapython import kerberos
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.common.text.converters import jsonify
|
||||
|
||||
try:
|
||||
from ipalib.x509 import Encoding
|
||||
@@ -106,15 +103,6 @@ else:
|
||||
from ipalib.x509 import load_certificate
|
||||
load_pem_x509_certificate = None
|
||||
|
||||
import socket
|
||||
import base64
|
||||
from ansible.module_utils import six
|
||||
|
||||
try:
|
||||
from collections.abc import Mapping # noqa
|
||||
except ImportError:
|
||||
from collections import Mapping # pylint: disable=deprecated-class
|
||||
|
||||
# Try to import is_ipa_configured or use a fallback implementation.
|
||||
try:
|
||||
from ipalib.facts import is_ipa_configured
|
||||
@@ -150,10 +138,30 @@ else:
|
||||
except ImportError:
|
||||
_dcerpc_bindings_installed = False # pylint: disable=invalid-name
|
||||
|
||||
if six.PY3:
|
||||
except ImportError as _err:
|
||||
ANSIBLE_FREEIPA_MODULE_IMPORT_ERROR = str(_err)
|
||||
|
||||
for attr in __all__:
|
||||
setattr(sys.modules[__name__], attr, None)
|
||||
|
||||
uuid = None
|
||||
netaddr = None
|
||||
is_ipa_configured = None
|
||||
load_certificate = None
|
||||
kerberos = None
|
||||
ipaserver = None # pylint: disable=C0103
|
||||
else:
|
||||
ANSIBLE_FREEIPA_MODULE_IMPORT_ERROR = None
|
||||
|
||||
|
||||
# ansible-freeipa requires locale to be C, IPA requires utf-8.
|
||||
os.environ["LANGUAGE"] = "C"
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
def valid_creds(module, principal): # noqa
|
||||
|
||||
def valid_creds(module, principal): # noqa
|
||||
"""Get valid credentials matching the princial, try GSSAPI first."""
|
||||
if "KRB5CCNAME" in os.environ:
|
||||
ccache = os.environ["KRB5CCNAME"]
|
||||
@@ -190,7 +198,8 @@ else:
|
||||
return True
|
||||
return False
|
||||
|
||||
def temp_kinit(principal, password):
|
||||
|
||||
def temp_kinit(principal, password):
|
||||
"""Kinit with password using a temporary ccache."""
|
||||
if not password:
|
||||
raise RuntimeError("The password is not set")
|
||||
@@ -203,12 +212,13 @@ else:
|
||||
try:
|
||||
kinit_password(principal, password, ccache_name)
|
||||
except RuntimeError as e:
|
||||
raise RuntimeError("Kerberos authentication failed: {}".format(e))
|
||||
raise RuntimeError("Kerberos authentication failed: %s" % str(e))
|
||||
|
||||
os.environ["KRB5CCNAME"] = ccache_name
|
||||
return ccache_dir, ccache_name
|
||||
|
||||
def temp_kdestroy(ccache_dir, ccache_name):
|
||||
|
||||
def temp_kdestroy(ccache_dir, ccache_name):
|
||||
"""Destroy temporary ticket and remove temporary ccache."""
|
||||
if ccache_name is not None:
|
||||
run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
|
||||
@@ -216,7 +226,8 @@ else:
|
||||
if ccache_dir is not None:
|
||||
shutil.rmtree(ccache_dir, ignore_errors=True)
|
||||
|
||||
def api_connect(context=None, **overrides):
|
||||
|
||||
def api_connect(context=None, **overrides):
|
||||
"""
|
||||
Initialize IPA API with the provided configuration.
|
||||
|
||||
@@ -270,23 +281,28 @@ else:
|
||||
if not backend.isconnected():
|
||||
backend.connect(ccache=os.environ.get('KRB5CCNAME', None))
|
||||
|
||||
def api_command(_module, command, name, args):
|
||||
|
||||
def api_command(_module, command, name, args):
|
||||
"""Call ipa.Command."""
|
||||
return api.Command[command](name, **args)
|
||||
|
||||
def api_command_no_name(_module, command, args):
|
||||
|
||||
def api_command_no_name(_module, command, args):
|
||||
"""Call ipa.Command without a name."""
|
||||
return api.Command[command](**args)
|
||||
|
||||
def api_check_command(command):
|
||||
|
||||
def api_check_command(command):
|
||||
"""Return if command exists in command list."""
|
||||
return command in api.Command
|
||||
|
||||
def api_check_param(command, name):
|
||||
|
||||
def api_check_param(command, name):
|
||||
"""Check if param exists in command param list."""
|
||||
return name in api.Command[command].params
|
||||
|
||||
def api_check_ipa_version(oper, requested_version):
|
||||
|
||||
def api_check_ipa_version(oper, requested_version):
|
||||
"""
|
||||
Compare the installed IPA version against a requested version.
|
||||
|
||||
@@ -306,7 +322,8 @@ else:
|
||||
return operation(tasks.parse_ipa_version(VERSION),
|
||||
tasks.parse_ipa_version(requested_version))
|
||||
|
||||
def date_format(value):
|
||||
|
||||
def date_format(value):
|
||||
accepted_date_formats = [
|
||||
LDAP_GENERALIZED_TIME_FORMAT, # generalized time
|
||||
'%Y-%m-%dT%H:%M:%SZ', # ISO 8601, second precision
|
||||
@@ -323,7 +340,8 @@ else:
|
||||
pass
|
||||
raise ValueError("Invalid date '%s'" % value)
|
||||
|
||||
def compare_args_ipa(module, args, ipa, ignore=None): # noqa
|
||||
|
||||
def compare_args_ipa(module, args, ipa, ignore=None): # noqa
|
||||
"""Compare IPA object attributes against command arguments.
|
||||
|
||||
This function compares 'ipa' attributes with the 'args' the module
|
||||
@@ -427,7 +445,8 @@ else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _afm_convert(value):
|
||||
|
||||
def _afm_convert(value):
|
||||
if value is not None:
|
||||
if isinstance(value, list):
|
||||
return [_afm_convert(x) for x in value]
|
||||
@@ -439,7 +458,8 @@ else:
|
||||
|
||||
return value
|
||||
|
||||
def module_params_get(module, name, allow_empty_string=False):
|
||||
|
||||
def module_params_get(module, name, allow_empty_string=False):
|
||||
value = _afm_convert(module.params.get(name))
|
||||
|
||||
# Fail on empty strings in the list or if allow_empty_string is True
|
||||
@@ -463,7 +483,8 @@ else:
|
||||
|
||||
return value
|
||||
|
||||
def module_params_get_lowercase(module, name, allow_empty_string=False):
|
||||
|
||||
def module_params_get_lowercase(module, name, allow_empty_string=False):
|
||||
value = module_params_get(module, name, allow_empty_string)
|
||||
if isinstance(value, list):
|
||||
value = [v.lower() for v in value]
|
||||
@@ -471,21 +492,26 @@ else:
|
||||
value = value.lower()
|
||||
return value
|
||||
|
||||
def api_get_domain():
|
||||
|
||||
def api_get_domain():
|
||||
return api.env.domain
|
||||
|
||||
def ensure_fqdn(name, domain):
|
||||
|
||||
def ensure_fqdn(name, domain):
|
||||
if "." not in name:
|
||||
return "%s.%s" % (name, domain)
|
||||
return name
|
||||
|
||||
def api_get_realm():
|
||||
|
||||
def api_get_realm():
|
||||
return api.env.realm
|
||||
|
||||
def api_get_basedn():
|
||||
|
||||
def api_get_basedn():
|
||||
return api.env.basedn
|
||||
|
||||
def gen_add_del_lists(user_list, res_list):
|
||||
|
||||
def gen_add_del_lists(user_list, res_list):
|
||||
"""
|
||||
Generate the lists for the addition and removal of members.
|
||||
|
||||
@@ -505,7 +531,8 @@ else:
|
||||
|
||||
return add_list, del_list
|
||||
|
||||
def gen_add_list(user_list, res_list):
|
||||
|
||||
def gen_add_list(user_list, res_list):
|
||||
"""
|
||||
Generate add list for addition of new members.
|
||||
|
||||
@@ -521,7 +548,8 @@ else:
|
||||
|
||||
return list(set(user_list or []) - set(res_list or []))
|
||||
|
||||
def gen_intersection_list(user_list, res_list):
|
||||
|
||||
def gen_intersection_list(user_list, res_list):
|
||||
"""
|
||||
Generate the intersection list for removal of existing members.
|
||||
|
||||
@@ -537,7 +565,8 @@ else:
|
||||
|
||||
return list(set(res_list or []).intersection(set(user_list or [])))
|
||||
|
||||
def encode_certificate(cert):
|
||||
|
||||
def encode_certificate(cert):
|
||||
"""
|
||||
Encode a certificate using base64.
|
||||
|
||||
@@ -551,7 +580,8 @@ else:
|
||||
encoded = encoded.decode('ascii')
|
||||
return encoded
|
||||
|
||||
def load_cert_from_str(cert):
|
||||
|
||||
def load_cert_from_str(cert):
|
||||
cert = cert.strip()
|
||||
if not cert.startswith("-----BEGIN CERTIFICATE-----"):
|
||||
cert = "-----BEGIN CERTIFICATE-----\n" + cert
|
||||
@@ -564,7 +594,8 @@ else:
|
||||
cert = load_certificate(cert.encode('utf-8'))
|
||||
return cert
|
||||
|
||||
def DN_x500_text(text): # pylint: disable=invalid-name
|
||||
|
||||
def DN_x500_text(text): # pylint: disable=invalid-name
|
||||
if hasattr(DN, "x500_text"):
|
||||
return DN(text).x500_text()
|
||||
# Emulate x500_text
|
||||
@@ -572,7 +603,8 @@ else:
|
||||
dn.rdns = reversed(dn.rdns)
|
||||
return str(dn)
|
||||
|
||||
def is_valid_port(port):
|
||||
|
||||
def is_valid_port(port):
|
||||
if not isinstance(port, int):
|
||||
return False
|
||||
|
||||
@@ -581,7 +613,8 @@ else:
|
||||
|
||||
return False
|
||||
|
||||
def is_ip_address(ipaddr):
|
||||
|
||||
def is_ip_address(ipaddr):
|
||||
"""Test if given IP address is a valid IPv4 or IPv6 address."""
|
||||
try:
|
||||
netaddr.IPAddress(str(ipaddr))
|
||||
@@ -589,7 +622,8 @@ else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_ip_network_address(ipaddr):
|
||||
|
||||
def is_ip_network_address(ipaddr):
|
||||
"""Test if given IP address is a valid IPv4 or IPv6 address."""
|
||||
try:
|
||||
netaddr.IPNetwork(str(ipaddr))
|
||||
@@ -597,7 +631,8 @@ else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_ipv4_addr(ipaddr):
|
||||
|
||||
def is_ipv4_addr(ipaddr):
|
||||
"""Test if given IP address is a valid IPv4 address."""
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET, ipaddr)
|
||||
@@ -605,7 +640,8 @@ else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_ipv6_addr(ipaddr):
|
||||
|
||||
def is_ipv6_addr(ipaddr):
|
||||
"""Test if given IP address is a valid IPv6 address."""
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET6, ipaddr)
|
||||
@@ -613,7 +649,8 @@ else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def servicedelegation_normalize_principals(module, principal,
|
||||
|
||||
def servicedelegation_normalize_principals(module, principal,
|
||||
check_exists=False):
|
||||
"""
|
||||
Normalize servicedelegation principals.
|
||||
@@ -696,7 +733,8 @@ else:
|
||||
|
||||
return _principal
|
||||
|
||||
def exit_raw_json(module, **kwargs):
|
||||
|
||||
def exit_raw_json(module, **kwargs):
|
||||
"""
|
||||
Print the raw parameters in JSON format, without masking.
|
||||
|
||||
@@ -715,7 +753,8 @@ else:
|
||||
print(jsonify(kwargs))
|
||||
sys.exit(0)
|
||||
|
||||
def __get_domain_validator():
|
||||
|
||||
def __get_domain_validator():
|
||||
if not _dcerpc_bindings_installed:
|
||||
raise ipalib_errors.NotFound(
|
||||
reason=(
|
||||
@@ -739,7 +778,8 @@ else:
|
||||
|
||||
return domain_validator
|
||||
|
||||
def get_trusted_domain_sid_from_name(dom_name):
|
||||
|
||||
def get_trusted_domain_sid_from_name(dom_name):
|
||||
"""
|
||||
Given a trust domain name, returns the domain SID.
|
||||
|
||||
@@ -751,7 +791,8 @@ else:
|
||||
|
||||
return unicode(sid) if sid is not None else None
|
||||
|
||||
class IPAParamMapping(Mapping):
|
||||
|
||||
class IPAParamMapping(Mapping):
|
||||
"""
|
||||
Provides IPA API mapping to playbook parameters or computed values.
|
||||
|
||||
@@ -874,7 +915,8 @@ else:
|
||||
|
||||
return args
|
||||
|
||||
class IPAAnsibleModule(AnsibleModule):
|
||||
|
||||
class IPAAnsibleModule(AnsibleModule):
|
||||
"""
|
||||
IPA Ansible Module.
|
||||
|
||||
@@ -953,6 +995,9 @@ else:
|
||||
# pylint: disable=super-with-arguments
|
||||
super(IPAAnsibleModule, self).__init__(*args, **kwargs)
|
||||
|
||||
if ANSIBLE_FREEIPA_MODULE_IMPORT_ERROR is not None:
|
||||
self.fail_json(msg=ANSIBLE_FREEIPA_MODULE_IMPORT_ERROR)
|
||||
|
||||
@contextmanager
|
||||
def ipa_connect(self, context=None):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user