From 2aaabc77c46432df380b1364af415f22e9a58398 Mon Sep 17 00:00:00 2001
From: Rafael Guterres Jeffman <rjeffman@redhat.com>
Date: Fri, 9 Oct 2020 15:34:35 -0300
Subject: [PATCH] Add FreeIPA version check to
 module_utils.ansible_freeipa_module.

Some attribute values are only accepted for specific FreeIPA versions,
for example `self` for permission's `bindtype`. Although there are
options to check for command and parameter availability, there is no
check for verifying if a value should be accepted.

This patch add a function to evaluate the target FreeIPA host version,
by comparing a giver version to the current installed one.

The version evaluation uses Python packaging's version comparision,
which is compatible with PEP 440, if available. If not available, it
falls back to a string split, that will work for the most common cases,
but might fail for versions including strings with `rc` or `dev`, for
example.
---
 .../module_utils/ansible_freeipa_module.py    | 41 +++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py
index 43f96eb6..03cfe1f1 100644
--- a/plugins/module_utils/ansible_freeipa_module.py
+++ b/plugins/module_utils/ansible_freeipa_module.py
@@ -23,6 +23,7 @@
 
 
 import sys
+import operator
 import os
 import uuid
 import tempfile
@@ -30,6 +31,25 @@ import shutil
 import gssapi
 from datetime import datetime
 from pprint import pformat
+
+try:
+    from packaging import version
+except ImportError:
+    # If `packaging` not found, split version string for creating version
+    # object. Although it is not PEP 440 compliant, it will work for stable
+    # FreeIPA releases.
+    import re
+
+    class version:
+        @staticmethod
+        def parse(version_str):
+            """
+            Split a version string A.B.C, into a tuple.
+
+            This will not work for `rc`, `dev` or similar version string.
+            """
+            return tuple(re.split("[-_\.]", version_str))  # noqa: W605
+
 from ipalib import api
 from ipalib import errors as ipalib_errors  # noqa
 from ipalib.config import Env
@@ -41,6 +61,7 @@ except ImportError:
     from ipapython.ipautil import kinit_password, kinit_keytab
 from ipapython.ipautil import run
 from ipapython.dn import DN
+from ipapython.version import VERSION
 from ipaplatform.paths import paths
 from ipalib.krb_utils import get_credentials_if_valid
 from ansible.module_utils.basic import AnsibleModule
@@ -187,6 +208,26 @@ def api_check_param(command, name):
     return name in api.Command[command].params
 
 
+def api_check_ipa_version(oper, requested_version):
+    """
+    Compare the installed IPA version against a requested version.
+
+    The valid operators are: <, <=, >, >=, ==, !=
+    """
+    oper_map = {
+        "<": operator.lt,
+        "<=": operator.le,
+        ">": operator.gt,
+        ">=": operator.ge,
+        "==": operator.eq,
+        "!=": operator.ne,
+    }
+    operation = oper_map.get(oper)
+    if not(operation):
+        raise NotImplementedError("Invalid operator: %s" % oper)
+    return operation(version.parse(VERSION), version.parse(requested_version))
+
+
 def execute_api_command(module, principal, password, command, name, args):
     """
     Execute an API command.
-- 
GitLab