Repository: openshift/openshift-ansible

Issue:
"template error while templating string: no filter named 'oo_version_gte_3_5_or_1_5'" when installing using release-1.5
When attempting to install using the `release-1.5` branch, I'm getting an error:

~~~
TASK [openshift_ca : Generate the loopback master client config] **********************************************************************************************
fatal: [openshift-master-2]: FAILED! => {"failed": true, "msg": "template error while templating string: no filter named 'oo_version_gte_3_5_or_1_5'. String: {{ hostvars[openshift_ca_host].openshift.common.client_binary
 }} adm create-api-client-config\n {% for named_ca_certificate in openshift.master.named_certificates | default([]) | oo_collect('cafile') %}\n --certificate-authority {{ named_ca_certificate }}\n {% endfor %}\n --certificate-authority={{ 
openshift_ca_cert }}\n --client-dir={{ openshift_ca_config_dir }}\n --groups=system:masters,system:openshift-master\n --master={{ hostvars[openshift_ca_host].openshift.master.loopback_api_url }}\n --public-master={{ hostvars[openshift_ca_h
ost].openshift.master.loopback_api_url }}\n --signer-cert={{ openshift_ca_cert }}\n --signer-key={{ openshift_ca_key }}\n --signer-serial={{ openshift_ca_serial }}\n --user=system:openshift-master\n --basename=openshift-master\n {% if open
shift_version | oo_version_gte_3_5_or_1_5(openshift.common.deployment_type) | bool %}\n --expire-days={{ openshift_master_cert_expire_days }}\n {% endif %}"}
~~~

This seems to be because #4467 backported a change that used code from [filter_plugins/openshift_version.py](https://github.com/openshift/openshift-ansible/blob/402e8caa5bcbc22050bb4a4f189a802262ca725f/filter_plugins/openshift_version.py), which isn't in `release-1.5`.


Discussion:

Resolving patch:
diff --git a/filter_plugins/openshift_version.py b/filter_plugins/openshift_version.py
new file mode 100644
--- /dev/null
+++ b/filter_plugins/openshift_version.py
@@ -0,0 +1,129 @@
+#!/usr/bin/python
+
+# -*- coding: utf-8 -*-
+# vim: expandtab:tabstop=4:shiftwidth=4
+"""
+Custom version comparison filters for use in openshift-ansible
+"""
+
+# pylint can't locate distutils.version within virtualenv
+# https://github.com/PyCQA/pylint/issues/73
+# pylint: disable=no-name-in-module, import-error
+from distutils.version import LooseVersion
+
+
+def legacy_gte_function_builder(name, versions):
+    """
+    Build and return a version comparison function.
+
+    Ex: name = 'oo_version_gte_3_1_or_1_1'
+        versions = {'enterprise': '3.1', 'origin': '1.1'}
+
+        returns oo_version_gte_3_1_or_1_1, a function which based on the
+        version and deployment type will return true if the provided
+        version is greater than or equal to the function's version
+    """
+    enterprise_version = versions['enterprise']
+    origin_version = versions['origin']
+
+    def _gte_function(version, deployment_type):
+        """
+        Dynamic function created by gte_function_builder.
+
+        Ex: version = '3.1'
+            deployment_type = 'openshift-enterprise'
+            returns True/False
+        """
+        version_gte = False
+        if 'enterprise' in deployment_type:
+            if str(version) >= LooseVersion(enterprise_version):
+                version_gte = True
+        elif 'origin' in deployment_type:
+            if str(version) >= LooseVersion(origin_version):
+                version_gte = True
+        return version_gte
+    _gte_function.__name__ = name
+    return _gte_function
+
+
+def gte_function_builder(name, gte_version):
+    """
+    Build and return a version comparison function.
+
+    Ex: name = 'oo_version_gte_3_6'
+        version = '3.6'
+
+        returns oo_version_gte_3_6, a function which based on the
+        version will return true if the provided version is greater
+        than or equal to the function's version
+    """
+    def _gte_function(version):
+        """
+        Dynamic function created by gte_function_builder.
+
+        Ex: version = '3.1'
+            returns True/False
+        """
+        version_gte = False
+        if str(version) >= LooseVersion(gte_version):
+            version_gte = True
+        return version_gte
+    _gte_function.__name__ = name
+    return _gte_function
+
+
+# pylint: disable=too-few-public-methods
+class FilterModule(object):
+    """
+    Filters for version checking.
+    """
+    # Each element of versions is composed of (major, minor_start, minor_end)
+    # Origin began versioning 3.x with 3.6, so begin 3.x with 3.6.
+    versions = [(3, 6, 10)]
+
+    def __init__(self):
+        """
+        Creates a new FilterModule for ose version checking.
+        """
+        self._filters = {}
+
+        # For each set of (major, minor, minor_iterations)
+        for major, minor_start, minor_end in self.versions:
+            # For each minor version in the range
+            for minor in range(minor_start, minor_end):
+                # Create the function name
+                func_name = 'oo_version_gte_{}_{}'.format(major, minor)
+                # Create the function with the builder
+                func = gte_function_builder(func_name, "{}.{}.0".format(major, minor))
+                # Add the function to the mapping
+                self._filters[func_name] = func
+
+        # Create filters with special versioning requirements.
+        # Treat all Origin 1.x as special case.
+        legacy_filters = [{'name': 'oo_version_gte_3_1_or_1_1',
+                           'versions': {'enterprise': '3.0.2.905',
+                                        'origin': '1.1.0'}},
+                          {'name': 'oo_version_gte_3_1_1_or_1_1_1',
+                           'versions': {'enterprise': '3.1.1',
+                                        'origin': '1.1.1'}},
+                          {'name': 'oo_version_gte_3_2_or_1_2',
+                           'versions': {'enterprise': '3.1.1.901',
+                                        'origin': '1.2.0'}},
+                          {'name': 'oo_version_gte_3_3_or_1_3',
+                           'versions': {'enterprise': '3.3.0',
+                                        'origin': '1.3.0'}},
+                          {'name': 'oo_version_gte_3_4_or_1_4',
+                           'versions': {'enterprise': '3.4.0',
+                                        'origin': '1.4.0'}},
+                          {'name': 'oo_version_gte_3_5_or_1_5',
+                           'versions': {'enterprise': '3.5.0',
+                                        'origin': '1.5.0'}}]
+        for legacy_filter in legacy_filters:
+            self._filters[legacy_filter['name']] = legacy_gte_function_builder(legacy_filter['name'],
+                                                                               legacy_filter['versions'])
+
+    def filters(self):
+        """
+        Return the filters mapping.
+        """
+        return self._filters


Test patch:
diff --git a/test/openshift_version_tests.py b/test/openshift_version_tests.py
new file mode 100644
--- /dev/null
+++ b/test/openshift_version_tests.py
@@ -0,0 +1,72 @@
+""" Tests for the openshift_version Ansible filter module. """
+# pylint: disable=missing-docstring,invalid-name
+
+import os
+import sys
+import unittest
+
+sys.path = [os.path.abspath(os.path.dirname(__file__) + "/../filter_plugins/")] + sys.path
+
+# pylint: disable=import-error
+import openshift_version  # noqa: E402
+
+
+class OpenShiftVersionTests(unittest.TestCase):
+
+    openshift_version_filters = openshift_version.FilterModule()
+
+    # Static tests for legacy filters.
+    legacy_gte_tests = [{'name': 'oo_version_gte_3_1_or_1_1',
+                         'positive_enterprise_version': '3.2.0',
+                         'negative_enterprise_version': '3.0.0',
+                         'positive_origin_version': '1.2.0',
+                         'negative_origin_version': '1.0.0'},
+                        {'name': 'oo_version_gte_3_1_1_or_1_1_1',
+                         'positive_enterprise_version': '3.2.0',
+                         'negative_enterprise_version': '3.1.0',
+                         'positive_origin_version': '1.2.0',
+                         'negative_origin_version': '1.1.0'},
+                        {'name': 'oo_version_gte_3_2_or_1_2',
+                         'positive_enterprise_version': '3.3.0',
+                         'negative_enterprise_version': '3.1.0',
+                         'positive_origin_version': '1.3.0',
+                         'negative_origin_version': '1.1.0'},
+                        {'name': 'oo_version_gte_3_3_or_1_3',
+                         'positive_enterprise_version': '3.4.0',
+                         'negative_enterprise_version': '3.2.0',
+                         'positive_origin_version': '1.4.0',
+                         'negative_origin_version': '1.2.0'},
+                        {'name': 'oo_version_gte_3_4_or_1_4',
+                         'positive_enterprise_version': '3.5.0',
+                         'negative_enterprise_version': '3.3.0',
+                         'positive_origin_version': '1.5.0',
+                         'negative_origin_version': '1.3.0'},
+                        {'name': 'oo_version_gte_3_5_or_1_5',
+                         'positive_enterprise_version': '3.6.0',
+                         'negative_enterprise_version': '3.4.0',
+                         'positive_origin_version': '1.6.0',
+                         'negative_origin_version': '1.4.0'}]
+
+    def test_legacy_gte_filters(self):
+        for test in self.legacy_gte_tests:
+            for deployment_type in ['enterprise', 'origin']:
+                # Test negative case per deployment_type
+                self.assertFalse(
+                    self.openshift_version_filters._filters[test['name']](
+                        test["negative_{}_version".format(deployment_type)], deployment_type))
+                # Test positive case per deployment_type
+                self.assertTrue(
+                    self.openshift_version_filters._filters[test['name']](
+                        test["positive_{}_version".format(deployment_type)], deployment_type))
+
+    def test_gte_filters(self):
+        for major, minor_start, minor_end in self.openshift_version_filters.versions:
+            for minor in range(minor_start, minor_end):
+                # Test positive case
+                self.assertTrue(
+                    self.openshift_version_filters._filters["oo_version_gte_{}_{}".format(major, minor)](
+                        "{}.{}".format(major, minor + 1)))
+                # Test negative case
+                self.assertFalse(
+                    self.openshift_version_filters._filters["oo_version_gte_{}_{}".format(major, minor)](
+                        "{}.{}".format(major, minor)))