Blame plugins/module_utils/ansible_freeipa_module.py

Packit 8cb997
#!/usr/bin/python
Packit 8cb997
# -*- coding: utf-8 -*-
Packit 8cb997
Packit 8cb997
# Authors:
Packit Service 0f71a7
#   Sergio Oliveira Campos <seocam@redhat.com>
Packit 8cb997
#   Thomas Woerner <twoerner@redhat.com>
Packit 8cb997
#
Packit 8cb997
# Copyright (C) 2019  Red Hat
Packit 8cb997
# see file 'COPYING' for use and warranty information
Packit 8cb997
#
Packit 8cb997
# This program is free software; you can redistribute it and/or modify
Packit 8cb997
# it under the terms of the GNU General Public License as published by
Packit 8cb997
# the Free Software Foundation, either version 3 of the License, or
Packit 8cb997
# (at your option) any later version.
Packit 8cb997
#
Packit 8cb997
# This program is distributed in the hope that it will be useful,
Packit 8cb997
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 8cb997
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 8cb997
# GNU General Public License for more details.
Packit 8cb997
#
Packit 8cb997
# You should have received a copy of the GNU General Public License
Packit 8cb997
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit 8cb997
Packit 8cb997
Packit 8cb997
import os
Packit 8cb997
import uuid
Packit 8cb997
import tempfile
Packit 8cb997
import shutil
Packit 8cb997
import gssapi
Packit 8cb997
from datetime import datetime
Packit Service 0f71a7
from pprint import pformat
Packit 8cb997
from ipalib import api
Packit Service 0f71a7
from ipalib import errors as ipalib_errors  # noqa
Packit 8cb997
from ipalib.config import Env
Packit 8cb997
from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT
Packit Service 0f71a7
Packit 8cb997
try:
Packit 8cb997
    from ipalib.install.kinit import kinit_password, kinit_keytab
Packit 8cb997
except ImportError:
Packit 8cb997
    from ipapython.ipautil import kinit_password, kinit_keytab
Packit 8cb997
from ipapython.ipautil import run
Packit Service 0f71a7
from ipapython.dn import DN
Packit 8cb997
from ipaplatform.paths import paths
Packit 8cb997
from ipalib.krb_utils import get_credentials_if_valid
Packit Service 0f71a7
from ansible.module_utils.basic import AnsibleModule
Packit 8cb997
from ansible.module_utils._text import to_text
Packit Service 0f71a7
Packit 8cb997
try:
Packit 8cb997
    from ipalib.x509 import Encoding
Packit 8cb997
except ImportError:
Packit 8cb997
    from cryptography.hazmat.primitives.serialization import Encoding
Packit Service 0f71a7
Packit Service 0f71a7
try:
Packit Service 0f71a7
    from ipalib.x509 import load_pem_x509_certificate
Packit Service 0f71a7
except ImportError:
Packit Service 0f71a7
    from ipalib.x509 import load_certificate
Packit Service 0f71a7
    load_pem_x509_certificate = None
Packit Service 0f71a7
Packit Service 0f71a7
import socket
Packit 8cb997
import base64
Packit 8cb997
import six
Packit 8cb997
Packit Service 0f71a7
try:
Packit Service 0f71a7
    from collections.abc import Mapping  # noqa
Packit Service 0f71a7
except ImportError:
Packit Service 0f71a7
    from collections import Mapping  # noqa
Packit Service 0f71a7
Packit 8cb997
Packit 8cb997
if six.PY3:
Packit 8cb997
    unicode = str
Packit 8cb997
Packit 8cb997
Packit Service 0f71a7
def valid_creds(module, principal):  # noqa
Packit Service 0f71a7
    """Get valid credentials matching the princial, try GSSAPI first."""
Packit 8cb997
    if "KRB5CCNAME" in os.environ:
Packit 8cb997
        ccache = os.environ["KRB5CCNAME"]
Packit 8cb997
        module.debug('KRB5CCNAME set to %s' % ccache)
Packit 8cb997
Packit 8cb997
        try:
Packit 8cb997
            cred = gssapi.Credentials(usage='initiate',
Packit 8cb997
                                      store={'ccache': ccache})
Packit 8cb997
        except gssapi.raw.misc.GSSError as e:
Packit 8cb997
            module.fail_json(msg='Failed to find default ccache: %s' % e)
Packit 8cb997
        else:
Packit 8cb997
            module.debug("Using principal %s" % str(cred.name))
Packit 8cb997
            return True
Packit 8cb997
Packit 8cb997
    elif "KRB5_CLIENT_KTNAME" in os.environ:
Packit 8cb997
        keytab = os.environ.get('KRB5_CLIENT_KTNAME', None)
Packit 8cb997
        module.debug('KRB5_CLIENT_KTNAME set to %s' % keytab)
Packit 8cb997
Packit 8cb997
        ccache_name = "MEMORY:%s" % str(uuid.uuid4())
Packit 8cb997
        os.environ["KRB5CCNAME"] = ccache_name
Packit 8cb997
Packit 8cb997
        try:
Packit 8cb997
            cred = kinit_keytab(principal, keytab, ccache_name)
Packit 8cb997
        except gssapi.raw.misc.GSSError as e:
Packit 8cb997
            module.fail_json(msg='Kerberos authentication failed : %s' % e)
Packit 8cb997
        else:
Packit 8cb997
            module.debug("Using principal %s" % str(cred.name))
Packit 8cb997
            return True
Packit 8cb997
Packit 8cb997
    creds = get_credentials_if_valid()
Packit 8cb997
    if creds and \
Packit 8cb997
       creds.lifetime > 0 and \
Packit 8cb997
       "%s@" % principal in creds.name.display_as(creds.name.name_type):
Packit 8cb997
        return True
Packit 8cb997
    return False
Packit 8cb997
Packit 8cb997
Packit 8cb997
def temp_kinit(principal, password):
Packit Service 0f71a7
    """Kinit with password using a temporary ccache."""
Packit 8cb997
    if not password:
Packit 8cb997
        raise RuntimeError("The password is not set")
Packit 8cb997
    if not principal:
Packit 8cb997
        principal = "admin"
Packit 8cb997
Packit 8cb997
    ccache_dir = tempfile.mkdtemp(prefix='krbcc')
Packit 8cb997
    ccache_name = os.path.join(ccache_dir, 'ccache')
Packit 8cb997
Packit 8cb997
    try:
Packit 8cb997
        kinit_password(principal, password, ccache_name)
Packit 8cb997
    except RuntimeError as e:
Packit 8cb997
        raise RuntimeError("Kerberos authentication failed: {}".format(e))
Packit 8cb997
Packit Service 0f71a7
    os.environ["KRB5CCNAME"] = ccache_name
Packit 8cb997
    return ccache_dir, ccache_name
Packit 8cb997
Packit 8cb997
Packit 8cb997
def temp_kdestroy(ccache_dir, ccache_name):
Packit Service 0f71a7
    """Destroy temporary ticket and remove temporary ccache."""
Packit 8cb997
    if ccache_name is not None:
Packit 8cb997
        run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
Packit Service 0f71a7
        del os.environ['KRB5CCNAME']
Packit 8cb997
    if ccache_dir is not None:
Packit 8cb997
        shutil.rmtree(ccache_dir, ignore_errors=True)
Packit 8cb997
Packit 8cb997
Packit 8cb997
def api_connect(context=None):
Packit 8cb997
    """
Packit Service 0f71a7
    Initialize IPA API with the provided context.
Packit Service 0f71a7
Packit Service 0f71a7
    `context` can be any of:
Packit Service 0f71a7
        * `server` (default)
Packit Service 0f71a7
        * `ansible-freeipa`
Packit Service 0f71a7
        * `cli_installer`
Packit 8cb997
    """
Packit 8cb997
    env = Env()
Packit 8cb997
    env._bootstrap()
Packit 8cb997
    env._finalize_core(**dict(DEFAULT_CONFIG))
Packit 8cb997
Packit 8cb997
    # available contexts are 'server', 'ansible-freeipa' and 'cli_installer'
Packit 8cb997
    if context is None:
Packit 8cb997
        context = 'server'
Packit 8cb997
Packit 8cb997
    api.bootstrap(context=context, debug=env.debug, log=None)
Packit 8cb997
    api.finalize()
Packit 8cb997
Packit 8cb997
    if api.env.in_server:
Packit 8cb997
        backend = api.Backend.ldap2
Packit 8cb997
    else:
Packit 8cb997
        backend = api.Backend.rpcclient
Packit 8cb997
Packit 8cb997
    if not backend.isconnected():
Packit Service 0f71a7
        backend.connect(ccache=os.environ.get('KRB5CCNAME', None))
Packit 8cb997
Packit 8cb997
Packit 8cb997
def api_command(module, command, name, args):
Packit Service 0f71a7
    """Call ipa.Command."""
Packit 8cb997
    return api.Command[command](name, **args)
Packit 8cb997
Packit 8cb997
Packit 8cb997
def api_command_no_name(module, command, args):
Packit Service 0f71a7
    """Call ipa.Command without a name."""
Packit 8cb997
    return api.Command[command](**args)
Packit 8cb997
Packit 8cb997
Packit Service 0f71a7
def api_check_command(command):
Packit Service 0f71a7
    """Return if command exists in command list."""
Packit Service 0f71a7
    return command in api.Command
Packit Service 0f71a7
Packit Service 0f71a7
Packit 8cb997
def api_check_param(command, name):
Packit Service 0f71a7
    """Check if param exists in command param list."""
Packit 8cb997
    return name in api.Command[command].params
Packit 8cb997
Packit 8cb997
Packit 8cb997
def execute_api_command(module, principal, password, command, name, args):
Packit 8cb997
    """
Packit Service 0f71a7
    Execute an API command.
Packit Service 0f71a7
Packit 8cb997
    Get KRB ticket if not already there, initialize api, connect,
Packit 8cb997
    execute command and destroy ticket again if it has been created also.
Packit 8cb997
    """
Packit 8cb997
    ccache_dir = None
Packit 8cb997
    ccache_name = None
Packit 8cb997
    try:
Packit 8cb997
        if not valid_creds(module, principal):
Packit 8cb997
            ccache_dir, ccache_name = temp_kinit(principal, password)
Packit 8cb997
        api_connect()
Packit 8cb997
Packit 8cb997
        return api_command(module, command, name, args)
Packit 8cb997
    except Exception as e:
Packit 8cb997
        module.fail_json(msg=str(e))
Packit 8cb997
Packit 8cb997
    finally:
Packit 8cb997
        temp_kdestroy(ccache_dir, ccache_name)
Packit 8cb997
Packit 8cb997
Packit 8cb997
def date_format(value):
Packit 8cb997
    accepted_date_formats = [
Packit 8cb997
        LDAP_GENERALIZED_TIME_FORMAT,  # generalized time
Packit 8cb997
        '%Y-%m-%dT%H:%M:%SZ',  # ISO 8601, second precision
Packit 8cb997
        '%Y-%m-%dT%H:%MZ',     # ISO 8601, minute precision
Packit 8cb997
        '%Y-%m-%dZ',           # ISO 8601, date only
Packit 8cb997
        '%Y-%m-%d %H:%M:%SZ',  # non-ISO 8601, second precision
Packit 8cb997
        '%Y-%m-%d %H:%MZ',     # non-ISO 8601, minute precision
Packit 8cb997
    ]
Packit 8cb997
Packit 8cb997
    for date_format in accepted_date_formats:
Packit 8cb997
        try:
Packit 8cb997
            return datetime.strptime(value, date_format)
Packit 8cb997
        except ValueError:
Packit 8cb997
            pass
Packit 8cb997
    raise ValueError("Invalid date '%s'" % value)
Packit 8cb997
Packit 8cb997
Packit Service 0f71a7
def compare_args_ipa(module, args, ipa):  # noqa
Packit Service 0f71a7
    """Compare IPA obj attrs with the command args.
Packit Service 0f71a7
Packit Service 0f71a7
    This function compares IPA objects attributes with the args the
Packit Service 0f71a7
    module is intending to use to call a command. This is useful to know
Packit Service 0f71a7
    if call to IPA server will be needed or not.
Packit Service 0f71a7
    In other to compare we have to prepare the perform slight changes in
Packit Service 0f71a7
    data formats.
Packit Service 0f71a7
Packit Service 0f71a7
    Returns True if they are the same and False otherwise.
Packit Service 0f71a7
    """
Packit Service 0f71a7
    base_debug_msg = "Ansible arguments and IPA commands differed. "
Packit Service 0f71a7
Packit 8cb997
    for key in args.keys():
Packit 8cb997
        if key not in ipa:
Packit Service 0f71a7
            module.debug(
Packit Service 0f71a7
                base_debug_msg + "Command key not present in IPA: %s" % key
Packit Service 0f71a7
            )
Packit 8cb997
            return False
Packit 8cb997
        else:
Packit 8cb997
            arg = args[key]
Packit 8cb997
            ipa_arg = ipa[key]
Packit 8cb997
            # If ipa_arg is a list and arg is not, replace arg
Packit 8cb997
            # with list containing arg. Most args in a find result
Packit 8cb997
            # are lists, but not all.
Packit 8cb997
            if isinstance(ipa_arg, tuple):
Packit 8cb997
                ipa_arg = list(ipa_arg)
Packit 8cb997
            if isinstance(ipa_arg, list):
Packit 8cb997
                if not isinstance(arg, list):
Packit 8cb997
                    arg = [arg]
Packit Service 0f71a7
                if len(ipa_arg) != len(arg):
Packit Service 0f71a7
                    module.debug(
Packit Service 0f71a7
                        base_debug_msg
Packit Service 0f71a7
                        + "List length doesn't match for key %s: %d %d"
Packit Service 0f71a7
                        % (key, len(arg), len(ipa_arg),)
Packit Service 0f71a7
                    )
Packit Service 0f71a7
                    return False
Packit 8cb997
                if isinstance(ipa_arg[0], str) and isinstance(arg[0], int):
Packit 8cb997
                    arg = [to_text(_arg) for _arg in arg]
Packit 8cb997
                if isinstance(ipa_arg[0], unicode) and isinstance(arg[0], int):
Packit 8cb997
                    arg = [to_text(_arg) for _arg in arg]
Packit Service 0f71a7
            try:
Packit Service 0f71a7
                arg_set = set(arg)
Packit Service 0f71a7
                ipa_arg_set = set(ipa_arg)
Packit Service 0f71a7
            except TypeError:
Packit Service 0f71a7
                if arg != ipa_arg:
Packit Service 0f71a7
                    module.debug(
Packit Service 0f71a7
                        base_debug_msg
Packit Service 0f71a7
                        + "Different values: %s %s" % (arg, ipa_arg)
Packit Service 0f71a7
                    )
Packit Service 0f71a7
                    return False
Packit Service 0f71a7
            else:
Packit Service 0f71a7
                if arg_set != ipa_arg_set:
Packit Service 0f71a7
                    module.debug(
Packit Service 0f71a7
                        base_debug_msg
Packit Service 0f71a7
                        + "Different set content: %s %s"
Packit Service 0f71a7
                        % (arg_set, ipa_arg_set,)
Packit Service 0f71a7
                    )
Packit Service 0f71a7
                    return False
Packit 8cb997
    return True
Packit 8cb997
Packit 8cb997
Packit 8cb997
def _afm_convert(value):
Packit 8cb997
    if value is not None:
Packit 8cb997
        if isinstance(value, list):
Packit 8cb997
            return [_afm_convert(x) for x in value]
Packit 8cb997
        elif isinstance(value, dict):
Packit 8cb997
            return {_afm_convert(k): _afm_convert(v) for k, v in value.items()}
Packit 8cb997
        elif isinstance(value, str):
Packit 8cb997
            return to_text(value)
Packit 8cb997
        else:
Packit 8cb997
            return value
Packit 8cb997
    else:
Packit 8cb997
        return value
Packit 8cb997
Packit 8cb997
Packit 8cb997
def module_params_get(module, name):
Packit 8cb997
    return _afm_convert(module.params.get(name))
Packit 8cb997
Packit 8cb997
Packit 8cb997
def api_get_realm():
Packit 8cb997
    return api.env.realm
Packit 8cb997
Packit 8cb997
Packit 8cb997
def gen_add_del_lists(user_list, res_list):
Packit Service 0f71a7
    """Generate the lists for the addition and removal of members."""
Packit Service 0f71a7
    # The user list is None, therefore the parameter should not be touched
Packit Service 0f71a7
    if user_list is None:
Packit Service 0f71a7
        return [], []
Packit Service 0f71a7
Packit 8cb997
    add_list = list(set(user_list or []) - set(res_list or []))
Packit 8cb997
    del_list = list(set(res_list or []) - set(user_list or []))
Packit 8cb997
Packit 8cb997
    return add_list, del_list
Packit 8cb997
Packit 8cb997
Packit 8cb997
def encode_certificate(cert):
Packit 8cb997
    """
Packit Service 0f71a7
    Encode a certificate using base64.
Packit Service 0f71a7
Packit Service 0f71a7
    It also takes FreeIPA and Python versions into account.
Packit 8cb997
    """
Packit Service 0f71a7
    if isinstance(cert, (str, unicode, bytes)):
Packit 8cb997
        encoded = base64.b64encode(cert)
Packit 8cb997
    else:
Packit 8cb997
        encoded = base64.b64encode(cert.public_bytes(Encoding.DER))
Packit 8cb997
    if not six.PY2:
Packit 8cb997
        encoded = encoded.decode('ascii')
Packit 8cb997
    return encoded
Packit Service 0f71a7
Packit Service 0f71a7
Packit Service 0f71a7
def load_cert_from_str(cert):
Packit Service 0f71a7
    cert = cert.strip()
Packit Service 0f71a7
    if not cert.startswith("-----BEGIN CERTIFICATE-----"):
Packit Service 0f71a7
        cert = "-----BEGIN CERTIFICATE-----\n" + cert
Packit Service 0f71a7
    if not cert.endswith("-----END CERTIFICATE-----"):
Packit Service 0f71a7
        cert += "\n-----END CERTIFICATE-----"
Packit Service 0f71a7
Packit Service 0f71a7
    if load_pem_x509_certificate is not None:
Packit Service 0f71a7
        cert = load_pem_x509_certificate(cert.encode('utf-8'))
Packit Service 0f71a7
    else:
Packit Service 0f71a7
        cert = load_certificate(cert.encode('utf-8'))
Packit Service 0f71a7
    return cert
Packit Service 0f71a7
Packit Service 0f71a7
Packit Service 0f71a7
def DN_x500_text(text):
Packit Service 0f71a7
    if hasattr(DN, "x500_text"):
Packit Service 0f71a7
        return DN(text).x500_text()
Packit Service 0f71a7
    else:
Packit Service 0f71a7
        # Emulate x500_text
Packit Service 0f71a7
        dn = DN(text)
Packit Service 0f71a7
        dn.rdns = reversed(dn.rdns)
Packit Service 0f71a7
        return str(dn)
Packit Service 0f71a7
Packit Service 0f71a7
Packit Service 0f71a7
def is_valid_port(port):
Packit Service 0f71a7
    if not isinstance(port, int):
Packit Service 0f71a7
        return False
Packit Service 0f71a7
Packit Service 0f71a7
    if 1 <= port <= 65535:
Packit Service 0f71a7
        return True
Packit Service 0f71a7
Packit Service 0f71a7
    return False
Packit Service 0f71a7
Packit Service 0f71a7
Packit Service 0f71a7
def is_ipv4_addr(ipaddr):
Packit Service 0f71a7
    """Test if given IP address is a valid IPv4 address."""
Packit Service 0f71a7
    try:
Packit Service 0f71a7
        socket.inet_pton(socket.AF_INET, ipaddr)
Packit Service 0f71a7
    except socket.error:
Packit Service 0f71a7
        return False
Packit Service 0f71a7
    return True
Packit Service 0f71a7
Packit Service 0f71a7
Packit Service 0f71a7
def is_ipv6_addr(ipaddr):
Packit Service 0f71a7
    """Test if given IP address is a valid IPv6 address."""
Packit Service 0f71a7
    try:
Packit Service 0f71a7
        socket.inet_pton(socket.AF_INET6, ipaddr)
Packit Service 0f71a7
    except socket.error:
Packit Service 0f71a7
        return False
Packit Service 0f71a7
    return True
Packit Service 0f71a7
Packit Service 0f71a7
Packit Service 0f71a7
class AnsibleFreeIPAParams(Mapping):
Packit Service 0f71a7
    def __init__(self, ansible_module):
Packit Service 0f71a7
        self.mapping = ansible_module.params
Packit Service 0f71a7
        self.ansible_module = ansible_module
Packit Service 0f71a7
Packit Service 0f71a7
    def __getitem__(self, key):
Packit Service 0f71a7
        param = self.mapping[key]
Packit Service 0f71a7
        if param is not None:
Packit Service 0f71a7
            return _afm_convert(param)
Packit Service 0f71a7
Packit Service 0f71a7
    def __iter__(self):
Packit Service 0f71a7
        return iter(self.mapping)
Packit Service 0f71a7
Packit Service 0f71a7
    def __len__(self):
Packit Service 0f71a7
        return len(self.mapping)
Packit Service 0f71a7
Packit Service 0f71a7
    @property
Packit Service 0f71a7
    def names(self):
Packit Service 0f71a7
        return self.name
Packit Service 0f71a7
Packit Service 0f71a7
    def __getattr__(self, name):
Packit Service 0f71a7
        return self.get(name)
Packit Service 0f71a7
Packit Service 0f71a7
Packit Service 0f71a7
class FreeIPABaseModule(AnsibleModule):
Packit Service 0f71a7
    """
Packit Service 0f71a7
    Base class for FreeIPA Ansible modules.
Packit Service 0f71a7
Packit Service 0f71a7
    Provides methods useful methods to be used by our modules.
Packit Service 0f71a7
Packit Service 0f71a7
    This class should be overriten and instantiated for the module.
Packit Service 0f71a7
    A basic implementation of an Ansible FreeIPA module expects its
Packit Service 0f71a7
    class to:
Packit Service 0f71a7
Packit Service 0f71a7
    1. Define a class attribute ``ipa_param_mapping``
Packit Service 0f71a7
    2. Implement the method ``define_ipa_commands()``
Packit Service 0f71a7
    3. Implement the method ``check_ipa_params()`` (optional)
Packit Service 0f71a7
Packit Service 0f71a7
    After instantiating the class the method ``ipa_run()`` should be called.
Packit Service 0f71a7
Packit Service 0f71a7
    Example (ansible-freeipa/plugins/modules/ipasomemodule.py):
Packit Service 0f71a7
Packit Service 0f71a7
    class SomeIPAModule(FreeIPABaseModule):
Packit Service 0f71a7
        ipa_param_mapping = {
Packit Service 0f71a7
            "arg_to_be_passed_to_ipa_command": "module_param",
Packit Service 0f71a7
            "another_arg": "get_another_module_param",
Packit Service 0f71a7
        }
Packit Service 0f71a7
Packit Service 0f71a7
        def get_another_module_param(self):
Packit Service 0f71a7
            another_module_param = self.ipa_params.another_module_param
Packit Service 0f71a7
            # Validate or modify another_module_param
Packit Service 0f71a7
            # ...
Packit Service 0f71a7
            return another_module_param
Packit Service 0f71a7
Packit Service 0f71a7
        def check_ipa_params(self):
Packit Service 0f71a7
            # Validate your params here
Packit Service 0f71a7
            # Example:
Packit Service 0f71a7
            if not self.ipa_params.module_param in VALID_OPTIONS:
Packit Service 0f71a7
                self.fail_json(msg="Invalid value for argument module_param")
Packit Service 0f71a7
Packit Service 0f71a7
        def define_ipa_commands(self):
Packit Service 0f71a7
            args = self.get_ipa_command_args()
Packit Service 0f71a7
Packit Service 0f71a7
            self.add_ipa_command(
Packit Service 0f71a7
                "some_ipa_command",
Packit Service 0f71a7
                name="obj-name",
Packit Service 0f71a7
                args=args,
Packit Service 0f71a7
            )
Packit Service 0f71a7
Packit Service 0f71a7
    def main():
Packit Service 0f71a7
        ipa_module = SomeIPAModule(argument_spec=dict(
Packit Service 0f71a7
            module_param=dict(
Packit Service 0f71a7
                type="str",
Packit Service 0f71a7
                default=None,
Packit Service 0f71a7
                required=False,
Packit Service 0f71a7
            ),
Packit Service 0f71a7
            another_module_param=dict(
Packit Service 0f71a7
                type="str",
Packit Service 0f71a7
                default=None,
Packit Service 0f71a7
                required=False,
Packit Service 0f71a7
            ),
Packit Service 0f71a7
        ))
Packit Service 0f71a7
        ipa_module.ipa_run()
Packit Service 0f71a7
Packit Service 0f71a7
    if __name__ == "__main__":
Packit Service 0f71a7
        main()
Packit Service 0f71a7
Packit Service 0f71a7
    """
Packit Service 0f71a7
Packit Service 0f71a7
    ipa_param_mapping = None
Packit Service 0f71a7
Packit Service 0f71a7
    def __init__(self, *args, **kwargs):
Packit Service 0f71a7
        super(FreeIPABaseModule, self).__init__(*args, **kwargs)
Packit Service 0f71a7
Packit Service 0f71a7
        # Attributes to store kerberos credentials (if needed)
Packit Service 0f71a7
        self.ccache_dir = None
Packit Service 0f71a7
        self.ccache_name = None
Packit Service 0f71a7
Packit Service 0f71a7
        # Status of an execution. Will be changed to True
Packit Service 0f71a7
        #   if something is actually peformed.
Packit Service 0f71a7
        self.changed = False
Packit Service 0f71a7
Packit Service 0f71a7
        # Status of the connection with the IPA server.
Packit Service 0f71a7
        # We need to know if the connection was actually stablished
Packit Service 0f71a7
        #   before we start sending commands.
Packit Service 0f71a7
        self.ipa_connected = False
Packit Service 0f71a7
Packit Service 0f71a7
        # Commands to be executed
Packit Service 0f71a7
        self.ipa_commands = []
Packit Service 0f71a7
Packit Service 0f71a7
        # Module exit arguments.
Packit Service 0f71a7
        self.exit_args = {}
Packit Service 0f71a7
Packit Service 0f71a7
        # Wrapper around the AnsibleModule.params.
Packit Service 0f71a7
        # Return the actual params but performing transformations
Packit Service 0f71a7
        #   when needed.
Packit Service 0f71a7
        self.ipa_params = AnsibleFreeIPAParams(self)
Packit Service 0f71a7
Packit Service 68089a
    def get_ipa_command_args(self, **kwargs):
Packit Service 0f71a7
        """
Packit Service 0f71a7
        Return a dict to be passed to an IPA command.
Packit Service 0f71a7
Packit Service 0f71a7
        The keys of ``ipa_param_mapping`` are also the keys of the return dict.
Packit Service 0f71a7
Packit Service 0f71a7
        The values of ``ipa_param_mapping`` needs to be either:
Packit Service 0f71a7
            * A str with the name of a defined method; or
Packit Service 0f71a7
            * A key of ``AnsibleModule.param``.
Packit Service 0f71a7
Packit Service 0f71a7
        In case of a method the return of the method will be set as value
Packit Service 0f71a7
        for the return dict.
Packit Service 0f71a7
Packit Service 0f71a7
        In case of a AnsibleModule.param the value of the param will be
Packit Service 0f71a7
        set in the return dict. In addition to that boolean values will be
Packit Service 0f71a7
        automaticaly converted to uppercase strings (as required by FreeIPA
Packit Service 0f71a7
        server).
Packit Service 0f71a7
Packit Service 0f71a7
        """
Packit Service 0f71a7
        args = {}
Packit Service 0f71a7
        for ipa_param_name, param_name in self.ipa_param_mapping.items():
Packit Service 0f71a7
Packit Service 0f71a7
            # Check if param_name is actually a param
Packit Service 0f71a7
            if param_name in self.ipa_params:
Packit Service 0f71a7
                value = self.ipa_params.get(param_name)
Packit Service 0f71a7
                if isinstance(value, bool):
Packit Service 0f71a7
                    value = "TRUE" if value else "FALSE"
Packit Service 0f71a7
Packit Service 0f71a7
            # Since param wasn't a param check if it's a method name
Packit Service 0f71a7
            elif hasattr(self, param_name):
Packit Service 0f71a7
                method = getattr(self, param_name)
Packit Service 0f71a7
                if callable(method):
Packit Service 68089a
                    value = method(**kwargs)
Packit Service 0f71a7
Packit Service 0f71a7
            # We don't have a way to guess the value so fail.
Packit Service 0f71a7
            else:
Packit Service 0f71a7
                self.fail_json(
Packit Service 0f71a7
                    msg=(
Packit Service 0f71a7
                        "Couldn't get a value for '%s'. Option '%s' is not "
Packit Service 0f71a7
                        "a module argument neither a defined method."
Packit Service 0f71a7
                    )
Packit Service 0f71a7
                    % (ipa_param_name, param_name)
Packit Service 0f71a7
                )
Packit Service 0f71a7
Packit Service 0f71a7
            if value is not None:
Packit Service 0f71a7
                args[ipa_param_name] = value
Packit Service 0f71a7
Packit Service 0f71a7
        return args
Packit Service 0f71a7
Packit Service 0f71a7
    def check_ipa_params(self):
Packit Service 0f71a7
        """Validate ipa_params before command is called."""
Packit Service 0f71a7
        pass
Packit Service 0f71a7
Packit Service 0f71a7
    def define_ipa_commands(self):
Packit Service 0f71a7
        """Define commands that will be run in IPA server."""
Packit Service 0f71a7
        raise NotImplementedError
Packit Service 0f71a7
Packit Service 0f71a7
    def api_command(self, command, name=None, args=None):
Packit Service 0f71a7
        """Execute a single command in IPA server."""
Packit Service 0f71a7
        if args is None:
Packit Service 0f71a7
            args = {}
Packit Service 0f71a7
Packit Service 0f71a7
        if name is None:
Packit Service 0f71a7
            return api_command_no_name(self, command, args)
Packit Service 0f71a7
Packit Service 0f71a7
        return api_command(self, command, name, args)
Packit Service 0f71a7
Packit Service 0f71a7
    def __enter__(self):
Packit Service 0f71a7
        """
Packit Service 0f71a7
        Connect to IPA server.
Packit Service 0f71a7
Packit Service 0f71a7
        Check the there are working Kerberos credentials to connect to
Packit Service 0f71a7
        IPA server. If there are not we perform a temporary kinit
Packit Service 0f71a7
        that will be terminated when exiting the context.
Packit Service 0f71a7
Packit Service 0f71a7
        If the connection fails ``ipa_connected`` attribute will be set
Packit Service 0f71a7
        to False.
Packit Service 0f71a7
        """
Packit Service 0f71a7
        principal = self.ipa_params.ipaadmin_principal
Packit Service 0f71a7
        password = self.ipa_params.ipaadmin_password
Packit Service 0f71a7
Packit Service 0f71a7
        try:
Packit Service 0f71a7
            if not valid_creds(self, principal):
Packit Service 0f71a7
                self.ccache_dir, self.ccache_name = temp_kinit(
Packit Service 0f71a7
                    principal, password,
Packit Service 0f71a7
                )
Packit Service 0f71a7
Packit Service 0f71a7
            api_connect()
Packit Service 0f71a7
Packit Service 0f71a7
        except Exception as excpt:
Packit Service 0f71a7
            self.fail_json(msg=str(excpt))
Packit Service 0f71a7
        else:
Packit Service 0f71a7
            self.ipa_connected = True
Packit Service 0f71a7
Packit Service 0f71a7
        return self
Packit Service 0f71a7
Packit Service 0f71a7
    def __exit__(self, exc_type, exc_val, exc_tb):
Packit Service 0f71a7
        """
Packit Service 0f71a7
        Terminate a connection with the IPA server.
Packit Service 0f71a7
Packit Service 0f71a7
        Deal with exceptions, destroy temporary kinit credentials and
Packit Service 0f71a7
        exit the module with proper arguments.
Packit Service 0f71a7
Packit Service 0f71a7
        """
Packit Service 0f71a7
        # TODO: shouldn't we also disconnect from api backend?
Packit Service 0f71a7
        temp_kdestroy(self.ccache_dir, self.ccache_name)
Packit Service 0f71a7
Packit Service b7ed33
        if exc_type == SystemExit:
Packit Service b7ed33
            raise
Packit Service b7ed33
Packit Service b7ed33
        if exc_val:
Packit Service b7ed33
            self.fail_json(msg=str(exc_val))
Packit Service b7ed33
Packit Service 0f71a7
        self.exit_json(changed=self.changed, user=self.exit_args)
Packit Service 0f71a7
Packit Service 0f71a7
    def get_command_errors(self, command, result):
Packit Service 0f71a7
        """Look for erros into command results."""
Packit Service 0f71a7
        # Get all errors
Packit Service 0f71a7
        # All "already a member" and "not a member" failures in the
Packit Service 0f71a7
        # result are ignored. All others are reported.
Packit Service 0f71a7
        errors = []
Packit Service 0f71a7
        for item in result.get("failed", tuple()):
Packit Service 0f71a7
            failed_item = result["failed"][item]
Packit Service 0f71a7
            for member_type in failed_item:
Packit Service 0f71a7
                for member, failure in failed_item[member_type]:
Packit Service 0f71a7
                    if (
Packit Service 0f71a7
                        "already a member" in failure
Packit Service 0f71a7
                        or "not a member" in failure
Packit Service 0f71a7
                    ):
Packit Service 0f71a7
                        continue
Packit Service 0f71a7
                    errors.append(
Packit Service 0f71a7
                        "%s: %s %s: %s"
Packit Service 0f71a7
                        % (command, member_type, member, failure)
Packit Service 0f71a7
                    )
Packit Service 0f71a7
Packit Service 0f71a7
        if len(errors) > 0:
Packit Service 0f71a7
            self.fail_json(", ".join("errors"))
Packit Service 0f71a7
Packit Service 0f71a7
    def add_ipa_command(self, command, name=None, args=None):
Packit Service 0f71a7
        """Add a command to the list of commands to be executed."""
Packit Service 0f71a7
        self.ipa_commands.append((name, command, args or {}))
Packit Service 0f71a7
Packit Service 0f71a7
    def _run_ipa_commands(self):
Packit Service 0f71a7
        """Execute commands in self.ipa_commands."""
Packit Service 0f71a7
        result = None
Packit Service 0f71a7
Packit Service 0f71a7
        for name, command, args in self.ipa_commands:
Packit Service 0f71a7
            try:
Packit Service 0f71a7
                result = self.api_command(command, name, args)
Packit Service 0f71a7
            except Exception as excpt:
Packit Service 0f71a7
                self.fail_json(msg="%s: %s: %s" % (command, name, str(excpt)))
Packit Service 0f71a7
            else:
Packit Service 0f71a7
                if "completed" in result:
Packit Service 0f71a7
                    if result["completed"] > 0:
Packit Service 0f71a7
                        self.changed = True
Packit Service 0f71a7
                else:
Packit Service 0f71a7
                    self.changed = True
Packit Service 0f71a7
Packit Service 0f71a7
            self.get_command_errors(command, result)
Packit Service 0f71a7
Packit Service 0f71a7
    def require_ipa_attrs_change(self, command_args, ipa_attrs):
Packit Service 0f71a7
        """
Packit Service 0f71a7
        Compare given args with current object attributes.
Packit Service 0f71a7
Packit Service 0f71a7
        Returns True in case current IPA object attributes differ from
Packit Service 0f71a7
        args passed to the module.
Packit Service 0f71a7
        """
Packit Service 0f71a7
        equal = compare_args_ipa(self, command_args, ipa_attrs)
Packit Service 0f71a7
        return not equal
Packit Service 0f71a7
Packit Service 0f71a7
    def pdebug(self, value):
Packit Service 0f71a7
        """Debug with pretty formatting."""
Packit Service 0f71a7
        self.debug(pformat(value))
Packit Service 0f71a7
Packit Service 0f71a7
    def ipa_run(self):
Packit Service 0f71a7
        """Execute module actions."""
Packit Service 0f71a7
        with self:
Packit Service 0f71a7
            if not self.ipa_connected:
Packit Service 0f71a7
                return
Packit Service 0f71a7
Packit Service 0f71a7
            self.check_ipa_params()
Packit Service 0f71a7
            self.define_ipa_commands()
Packit Service 0f71a7
            self._run_ipa_commands()