Repository: openshift/openshift-ansible

Issue:
Idempotency issues to fix
```
...
TASK: [Generate session authentication key] *********************************** 
changed: [ose3-master-ansible.test.example.com] => (item=1)

TASK: [Generate session encryption key] *************************************** 
changed: [ose3-master-ansible.test.example.com] => (item=1)
...
TASK: [openshift_manage_node | Set node schedulability] *********************** 
changed: [ose3-master-ansible.test.example.com] => (item={u'': u'', 'session_auth_output': {'msg': 'All items completed', 'changed': True, 'results': [{'cmd': ['/usr/bin/openssl', 'rand', '-base64', '24'], 'end': '2015-11-20 22:24:22.
...
```



Discussion:

Resolving patch:
diff --git a/filter_plugins/oo_filters.py b/filter_plugins/oo_filters.py
--- a/filter_plugins/oo_filters.py
+++ b/filter_plugins/oo_filters.py
@@ -8,12 +8,11 @@
 from ansible import errors
 from operator import itemgetter
 import OpenSSL.crypto
-import os.path
+import os
 import pdb
 import re
 import json
 
-
 class FilterModule(object):
     ''' Custom ansible filters '''
 
@@ -366,9 +365,6 @@ def oo_parse_named_certificates(certificates, named_certs_dir, internal_hostname
                            "keyfile": "/etc/origin/master/named_certificates/custom2.key",
                            "names": [ "some-hostname.com" ] }]
         '''
-        if not issubclass(type(certificates), list):
-            raise errors.AnsibleFilterError("|failed expects certificates is a list")
-
         if not issubclass(type(named_certs_dir), unicode):
             raise errors.AnsibleFilterError("|failed expects named_certs_dir is unicode")
 
@@ -468,6 +464,16 @@ def _add_host(clusters,
                 pass
         return clusters
 
+    @staticmethod
+    def oo_generate_secret(num_bytes):
+        ''' generate a session secret '''
+
+        if not issubclass(type(num_bytes), int):
+            raise errors.AnsibleFilterError("|failed expects num_bytes is int")
+
+        secret = os.urandom(num_bytes)
+        return secret.encode('base-64').strip()
+
     def filters(self):
         ''' returns a mapping of filters to methods '''
         return {
@@ -486,5 +492,6 @@ def filters(self):
             "oo_parse_heat_stack_outputs": self.oo_parse_heat_stack_outputs,
             "oo_parse_named_certificates": self.oo_parse_named_certificates,
             "oo_haproxy_backend_masters": self.oo_haproxy_backend_masters,
-            "oo_pretty_print_cluster": self.oo_pretty_print_cluster
+            "oo_pretty_print_cluster": self.oo_pretty_print_cluster,
+            "oo_generate_secret": self.oo_generate_secret
         }
diff --git a/filter_plugins/openshift_master.py b/filter_plugins/openshift_master.py
--- a/filter_plugins/openshift_master.py
+++ b/filter_plugins/openshift_master.py
@@ -463,7 +463,6 @@ def translate_idps(idps, api_version):
         IdentityProviderBase.validate_idp_list(idp_list)
         return yaml.safe_dump([idp.to_dict() for idp in idp_list], default_flow_style=False)
 
-
     def filters(self):
         ''' returns a mapping of filters to methods '''
         return {"translate_idps": self.translate_idps}
diff --git a/roles/openshift_facts/library/openshift_facts.py b/roles/openshift_facts/library/openshift_facts.py
--- a/roles/openshift_facts/library/openshift_facts.py
+++ b/roles/openshift_facts/library/openshift_facts.py
@@ -1043,6 +1043,7 @@ class OpenShiftFacts(object):
             facts (dict): facts for the host
 
         Args:
+            module (AnsibleModule): an AnsibleModule object
             role (str): role for setting local facts
             filename (str): local facts file to use
             local_facts (dict): local facts to set
@@ -1257,14 +1258,78 @@ def init_local_facts(self, facts=None, additive_facts_to_overwrite=False):
                 del facts[key]
 
         if new_local_facts != local_facts:
+            self.validate_local_facts(new_local_facts)
             changed = True
-
             if not module.check_mode:
                 save_local_facts(self.filename, new_local_facts)
 
         self.changed = changed
         return new_local_facts
 
+    def validate_local_facts(self, facts=None):
+        """ Validate local facts
+
+            Args:
+                facts (dict): local facts to validate
+        """
+        invalid_facts = dict()
+        invalid_facts = self.validate_master_facts(facts, invalid_facts)
+        if invalid_facts:
+            msg = 'Invalid facts detected:\n'
+            for key in invalid_facts.keys():
+                msg += '{0}: {1}\n'.format(key, invalid_facts[key])
+            module.fail_json(msg=msg,
+                             changed=self.changed)
+
+    # disabling pylint errors for line-too-long since we're dealing
+    # with best effort reduction of error messages here.
+    # disabling errors for too-many-branches since we require checking
+    # many conditions.
+    # pylint: disable=line-too-long, too-many-branches
+    @staticmethod
+    def validate_master_facts(facts, invalid_facts):
+        """ Validate master facts
+
+            Args:
+                facts (dict): local facts to validate
+                invalid_facts (dict): collected invalid_facts
+
+            Returns:
+                dict: Invalid facts
+        """
+        if 'master' in facts:
+            # openshift.master.session_auth_secrets
+            if 'session_auth_secrets' in facts['master']:
+                session_auth_secrets = facts['master']['session_auth_secrets']
+                if not issubclass(type(session_auth_secrets), list):
+                    invalid_facts['session_auth_secrets'] = 'Expects session_auth_secrets is a list.'
+                elif 'session_encryption_secrets' not in facts['master']:
+                    invalid_facts['session_auth_secrets'] = ('openshift_master_session_encryption secrets must be set '
+                                                             'if openshift_master_session_auth_secrets is provided.')
+                elif len(session_auth_secrets) != len(facts['master']['session_encryption_secrets']):
+                    invalid_facts['session_auth_secrets'] = ('openshift_master_session_auth_secrets and '
+                                                             'openshift_master_session_encryption_secrets must be '
+                                                             'equal length.')
+                else:
+                    for secret in session_auth_secrets:
+                        if len(secret) < 32:
+                            invalid_facts['session_auth_secrets'] = ('Invalid secret in session_auth_secrets. '
+                                                                     'Secrets must be at least 32 characters in length.')
+            # openshift.master.session_encryption_secrets
+            if 'session_encryption_secrets' in facts['master']:
+                session_encryption_secrets = facts['master']['session_encryption_secrets']
+                if not issubclass(type(session_encryption_secrets), list):
+                    invalid_facts['session_encryption_secrets'] = 'Expects session_encryption_secrets is a list.'
+                elif 'session_auth_secrets' not in facts['master']:
+                    invalid_facts['session_encryption_secrets'] = ('openshift_master_session_auth_secrets must be '
+                                                                   'set if openshift_master_session_encryption_secrets '
+                                                                   'is provided.')
+                else:
+                    for secret in session_encryption_secrets:
+                        if len(secret) not in [16, 24, 32]:
+                            invalid_facts['session_encryption_secrets'] = ('Invalid secret in session_encryption_secrets. '
+                                                                           'Secrets must be 16, 24, or 32 characters in length.')
+        return invalid_facts
 
 def main():
     """ main """


Test patch: