Browse Source

Merge pull request #3628 from kwoodson/yedit_edits

Merged by openshift-bot
OpenShift Bot 8 years ago
parent
commit
c540fcaa85
34 changed files with 3557 additions and 2213 deletions
  1. 120 77
      roles/lib_openshift/library/oc_adm_ca_server_cert.py
  2. 120 77
      roles/lib_openshift/library/oc_adm_manage_node.py
  3. 120 77
      roles/lib_openshift/library/oc_adm_policy_group.py
  4. 120 77
      roles/lib_openshift/library/oc_adm_policy_user.py
  5. 120 77
      roles/lib_openshift/library/oc_adm_registry.py
  6. 120 77
      roles/lib_openshift/library/oc_adm_router.py
  7. 120 77
      roles/lib_openshift/library/oc_configmap.py
  8. 120 77
      roles/lib_openshift/library/oc_edit.py
  9. 120 77
      roles/lib_openshift/library/oc_env.py
  10. 120 77
      roles/lib_openshift/library/oc_group.py
  11. 120 77
      roles/lib_openshift/library/oc_image.py
  12. 120 77
      roles/lib_openshift/library/oc_label.py
  13. 120 77
      roles/lib_openshift/library/oc_obj.py
  14. 120 77
      roles/lib_openshift/library/oc_objectvalidator.py
  15. 120 77
      roles/lib_openshift/library/oc_process.py
  16. 120 77
      roles/lib_openshift/library/oc_project.py
  17. 120 77
      roles/lib_openshift/library/oc_pvc.py
  18. 120 77
      roles/lib_openshift/library/oc_route.py
  19. 120 77
      roles/lib_openshift/library/oc_scale.py
  20. 120 77
      roles/lib_openshift/library/oc_secret.py
  21. 120 77
      roles/lib_openshift/library/oc_service.py
  22. 120 77
      roles/lib_openshift/library/oc_serviceaccount.py
  23. 120 77
      roles/lib_openshift/library/oc_serviceaccount_secret.py
  24. 120 77
      roles/lib_openshift/library/oc_user.py
  25. 120 77
      roles/lib_openshift/library/oc_version.py
  26. 121 78
      roles/lib_openshift/library/oc_volume.py
  27. 1 1
      roles/lib_openshift/src/class/oc_volume.py
  28. 159 78
      roles/lib_utils/library/yedit.py
  29. 23 1
      roles/lib_utils/src/ansible/yedit.py
  30. 121 77
      roles/lib_utils/src/class/yedit.py
  31. 16 0
      roles/lib_utils/src/doc/yedit
  32. 0 52
      roles/lib_utils/src/test/integration/kube-manager-test.yaml.orig
  33. 30 1
      roles/lib_utils/src/test/integration/yedit.yml
  34. 86 0
      roles/lib_utils/src/test/unit/test_yedit.py

+ 120 - 77
roles/lib_openshift/library/oc_adm_ca_server_cert.py

@@ -155,8 +155,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/ca_server_cert -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -190,13 +188,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -212,13 +210,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -240,7 +238,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -329,7 +327,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -429,7 +427,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -548,8 +546,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -610,7 +608,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -636,7 +644,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -668,114 +676,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
+
+        state = params['state']
 
-        if module.params['src']:
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_adm_manage_node.py

@@ -141,8 +141,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/manage_node -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -176,13 +174,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -198,13 +196,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -226,7 +224,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -315,7 +313,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -415,7 +413,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -534,8 +532,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -596,7 +594,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -622,7 +630,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -654,114 +662,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
+
+        state = params['state']
 
-        if module.params['src']:
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
+
+            elif params['edits'] is not None:
+                edits = params['edits']
 
-                if rval[0] and module.params['src']:
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_adm_policy_group.py

@@ -127,8 +127,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/policy_group -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -162,13 +160,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -184,13 +182,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -212,7 +210,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -301,7 +299,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -401,7 +399,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -520,8 +518,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -582,7 +580,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -608,7 +616,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -640,114 +648,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
+
+        state = params['state']
 
-        if module.params['src']:
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_adm_policy_user.py

@@ -127,8 +127,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/policy_user -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -162,13 +160,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -184,13 +182,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -212,7 +210,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -301,7 +299,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -401,7 +399,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -520,8 +518,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -582,7 +580,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -608,7 +616,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -640,114 +648,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
+
+        state = params['state']
 
-        if module.params['src']:
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_adm_registry.py

@@ -245,8 +245,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/registry -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -280,13 +278,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -302,13 +300,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -330,7 +328,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -419,7 +417,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -519,7 +517,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -638,8 +636,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -700,7 +698,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -726,7 +734,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -758,114 +766,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
+
+        state = params['state']
 
-        if module.params['src']:
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_adm_router.py

@@ -270,8 +270,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/router -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -305,13 +303,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -327,13 +325,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -355,7 +353,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -444,7 +442,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -544,7 +542,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -663,8 +661,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -725,7 +723,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -751,7 +759,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -783,114 +791,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_configmap.py

@@ -125,8 +125,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/configmap -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -160,13 +158,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -182,13 +180,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -210,7 +208,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -299,7 +297,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -399,7 +397,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -518,8 +516,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -580,7 +578,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -606,7 +614,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -638,114 +646,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_edit.py

@@ -169,8 +169,6 @@ oc_edit:
 # -*- -*- -*- End included fragment: doc/edit -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -204,13 +202,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -226,13 +224,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -254,7 +252,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -343,7 +341,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -443,7 +441,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -562,8 +560,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -624,7 +622,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -650,7 +658,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -682,114 +690,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
+
+        state = params['state']
 
-        if module.params['src']:
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
+
+            elif params['edits'] is not None:
+                edits = params['edits']
 
-                if rval[0] and module.params['src']:
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_env.py

@@ -136,8 +136,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/env -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -171,13 +169,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -193,13 +191,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -221,7 +219,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -310,7 +308,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -410,7 +408,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -529,8 +527,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -591,7 +589,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -617,7 +625,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -649,114 +657,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_group.py

@@ -109,8 +109,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/group -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -144,13 +142,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -166,13 +164,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -194,7 +192,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -283,7 +281,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -383,7 +381,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -502,8 +500,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -564,7 +562,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -590,7 +598,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -622,114 +630,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_image.py

@@ -128,8 +128,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/image -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -163,13 +161,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -185,13 +183,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -213,7 +211,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -302,7 +300,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -402,7 +400,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -521,8 +519,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -583,7 +581,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -609,7 +617,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -641,114 +649,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_label.py

@@ -145,8 +145,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/label -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -180,13 +178,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -202,13 +200,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -230,7 +228,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -319,7 +317,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -419,7 +417,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -538,8 +536,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -600,7 +598,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -626,7 +634,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -658,114 +666,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_obj.py

@@ -148,8 +148,6 @@ register: router_output
 # -*- -*- -*- End included fragment: doc/obj -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -183,13 +181,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -205,13 +203,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -233,7 +231,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -322,7 +320,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -422,7 +420,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -541,8 +539,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -603,7 +601,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -629,7 +637,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -661,114 +669,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_objectvalidator.py

@@ -80,8 +80,6 @@ oc_objectvalidator:
 # -*- -*- -*- End included fragment: doc/objectvalidator -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -115,13 +113,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -137,13 +135,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -165,7 +163,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -254,7 +252,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -354,7 +352,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -473,8 +471,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -535,7 +533,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -561,7 +569,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -593,114 +601,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
+
+        state = params['state']
 
-        if module.params['src']:
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_process.py

@@ -137,8 +137,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/process -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -172,13 +170,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -194,13 +192,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -222,7 +220,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -311,7 +309,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -411,7 +409,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -530,8 +528,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -592,7 +590,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -618,7 +626,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -650,114 +658,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_project.py

@@ -134,8 +134,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/project -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -169,13 +167,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -191,13 +189,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -219,7 +217,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -308,7 +306,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -408,7 +406,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -527,8 +525,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -589,7 +587,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -615,7 +623,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -647,114 +655,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_pvc.py

@@ -129,8 +129,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/pvc -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -164,13 +162,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -186,13 +184,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -214,7 +212,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -303,7 +301,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -403,7 +401,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -522,8 +520,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -584,7 +582,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -610,7 +618,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -642,114 +650,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_route.py

@@ -179,8 +179,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/route -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -214,13 +212,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -236,13 +234,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -264,7 +262,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -353,7 +351,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -453,7 +451,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -572,8 +570,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -634,7 +632,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -660,7 +668,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -692,114 +700,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_scale.py

@@ -123,8 +123,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/scale -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -158,13 +156,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -180,13 +178,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -208,7 +206,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -297,7 +295,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -397,7 +395,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -516,8 +514,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -578,7 +576,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -604,7 +612,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -636,114 +644,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_secret.py

@@ -169,8 +169,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/secret -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -204,13 +202,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -226,13 +224,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -254,7 +252,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -343,7 +341,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -443,7 +441,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -562,8 +560,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -624,7 +622,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -650,7 +658,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -682,114 +690,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_service.py

@@ -175,8 +175,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/service -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -210,13 +208,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -232,13 +230,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -260,7 +258,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -349,7 +347,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -449,7 +447,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -568,8 +566,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -630,7 +628,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -656,7 +664,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -688,114 +696,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_serviceaccount.py

@@ -121,8 +121,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/serviceaccount -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -156,13 +154,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -178,13 +176,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -206,7 +204,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -295,7 +293,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -395,7 +393,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -514,8 +512,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -576,7 +574,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -602,7 +610,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -634,114 +642,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_serviceaccount_secret.py

@@ -121,8 +121,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/serviceaccount_secret -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -156,13 +154,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -178,13 +176,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -206,7 +204,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -295,7 +293,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -395,7 +393,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -514,8 +512,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -576,7 +574,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -602,7 +610,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -634,114 +642,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_user.py

@@ -181,8 +181,6 @@ ok: [ded-int-aws-master-61034] => {
 # -*- -*- -*- End included fragment: doc/user -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -216,13 +214,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -238,13 +236,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -266,7 +264,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -355,7 +353,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -455,7 +453,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -574,8 +572,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -636,7 +634,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -662,7 +670,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -694,114 +702,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 120 - 77
roles/lib_openshift/library/oc_version.py

@@ -93,8 +93,6 @@ oc_version:
 # -*- -*- -*- End included fragment: doc/version -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -128,13 +126,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -150,13 +148,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -178,7 +176,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -267,7 +265,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -367,7 +365,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -486,8 +484,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -548,7 +546,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -574,7 +582,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -606,114 +614,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
+
+        state = params['state']
 
-        if module.params['src']:
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-

+ 121 - 78
roles/lib_openshift/library/oc_volume.py

@@ -158,8 +158,6 @@ EXAMPLES = '''
 # -*- -*- -*- End included fragment: doc/volume -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -193,13 +191,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -215,13 +213,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -243,7 +241,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -332,7 +330,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -432,7 +430,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -551,8 +549,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -613,7 +611,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -639,7 +647,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -671,114 +679,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
@@ -1941,7 +1984,7 @@ class OCVolume(OpenShiftCLI):
             if not oc_volume.exists():
 
                 if check_mode:
-                    exit_json(changed=False, msg='Would have performed a create.')
+                    return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'}
 
                 # Create it here
                 api_rval = oc_volume.put()

+ 1 - 1
roles/lib_openshift/src/class/oc_volume.py

@@ -157,7 +157,7 @@ class OCVolume(OpenShiftCLI):
             if not oc_volume.exists():
 
                 if check_mode:
-                    exit_json(changed=False, msg='Would have performed a create.')
+                    return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'}
 
                 # Create it here
                 api_rval = oc_volume.put()

+ 159 - 78
roles/lib_utils/library/yedit.py

@@ -180,13 +180,27 @@ EXAMPLES = '''
 # a:
 #   b:
 #     c: d
+#
+# multiple edits at the same time
+- name: perform multiple edits
+  yedit:
+    src: somefile.yml
+    edits:
+    - key: a#b#c
+      value: d
+    - key: a#b#c#d
+      value: e
+    state: present
+# Results:
+# a:
+#   b:
+#     c:
+#       d: e
 '''
 
 # -*- -*- -*- End included fragment: doc/yedit -*- -*- -*-
 
 # -*- -*- -*- Begin included fragment: class/yedit.py -*- -*- -*-
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
 
 
 class YeditException(Exception):
@@ -220,13 +234,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -242,13 +256,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -270,7 +284,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -359,7 +373,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -459,7 +473,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -578,8 +592,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -640,7 +654,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -666,7 +690,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -698,114 +722,149 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
 
-        if module.params['src']:
+        state = params['state']
+
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
 
-                if rval[0] and module.params['src']:
+            elif params['edits'] is not None:
+                edits = params['edits']
+
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}
 
 # -*- -*- -*- End included fragment: class/yedit.py -*- -*- -*-
@@ -837,12 +896,34 @@ def main():
                                    type='str'),
             backup=dict(default=True, type='bool'),
             separator=dict(default='.', type='str'),
+            edits=dict(default=None, type='list'),
         ),
         mutually_exclusive=[["curr_value", "index"], ['update', "append"]],
         required_one_of=[["content", "src"]],
     )
 
-    rval = Yedit.run_ansible(module)
+    # Verify we recieved either a valid key or edits with valid keys when receiving a src file.
+    # A valid key being not None or not ''.
+    if module.params['src'] is not None:
+        key_error = False
+        edit_error = False
+
+        if module.params['key'] in [None, '']:
+            key_error = True
+
+        if module.params['edits'] in [None, []]:
+            edit_error = True
+
+        else:
+            for edit in module.params['edits']:
+                if edit.get('key') in [None, '']:
+                    edit_error = True
+                    break
+
+        if key_error and edit_error:
+            module.fail_json(failed=True, msg='Empty value for parameter key not allowed.')
+
+    rval = Yedit.run_ansible(module.params)
     if 'failed' in rval and rval['failed']:
         module.fail_json(**rval)
 

+ 23 - 1
roles/lib_utils/src/ansible/yedit.py

@@ -26,12 +26,34 @@ def main():
                                    type='str'),
             backup=dict(default=True, type='bool'),
             separator=dict(default='.', type='str'),
+            edits=dict(default=None, type='list'),
         ),
         mutually_exclusive=[["curr_value", "index"], ['update', "append"]],
         required_one_of=[["content", "src"]],
     )
 
-    rval = Yedit.run_ansible(module)
+    # Verify we recieved either a valid key or edits with valid keys when receiving a src file.
+    # A valid key being not None or not ''.
+    if module.params['src'] is not None:
+        key_error = False
+        edit_error = False
+
+        if module.params['key'] in [None, '']:
+            key_error = True
+
+        if module.params['edits'] in [None, []]:
+            edit_error = True
+
+        else:
+            for edit in module.params['edits']:
+                if edit.get('key') in [None, '']:
+                    edit_error = True
+                    break
+
+        if key_error and edit_error:
+            module.fail_json(failed=True, msg='Empty value for parameter key not allowed.')
+
+    rval = Yedit.run_ansible(module.params)
     if 'failed' in rval and rval['failed']:
         module.fail_json(**rval)
 

+ 121 - 77
roles/lib_utils/src/class/yedit.py

@@ -1,6 +1,5 @@
 # flake8: noqa
-# pylint: disable=undefined-variable,missing-docstring
-# noqa: E301,E302
+# pylint: skip-file
 
 
 class YeditException(Exception):
@@ -34,13 +33,13 @@ class Yedit(object):
 
     @property
     def separator(self):
-        ''' getter method for yaml_dict '''
+        ''' getter method for separator '''
         return self._separator
 
     @separator.setter
-    def separator(self):
-        ''' getter method for yaml_dict '''
-        return self._separator
+    def separator(self, inc_sep):
+        ''' setter method for separator '''
+        self._separator = inc_sep
 
     @property
     def yaml_dict(self):
@@ -56,13 +55,13 @@ class Yedit(object):
     def parse_key(key, sep='.'):
         '''parse the key allowing the appropriate separator'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        return re.findall(Yedit.re_key % ''.join(common_separators), key)
+        return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
 
     @staticmethod
     def valid_key(key, sep='.'):
         '''validate the incoming key'''
         common_separators = list(Yedit.com_sep - set([sep]))
-        if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
+        if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
             return False
 
         return True
@@ -84,7 +83,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes[:-1]:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -173,7 +172,7 @@ class Yedit(object):
         key_indexes = Yedit.parse_key(key, sep)
         for arr_ind, dict_key in key_indexes:
             if dict_key and isinstance(data, dict):
-                data = data.get(dict_key, None)
+                data = data.get(dict_key)
             elif (arr_ind and isinstance(data, list) and
                   int(arr_ind) <= len(data) - 1):
                 data = data[int(arr_ind)]
@@ -273,7 +272,7 @@ class Yedit(object):
                 self.yaml_dict = json.loads(contents)
         except yaml.YAMLError as err:
             # Error loading yaml or json
-            raise YeditException('Problem with loading yaml file. %s' % err)
+            raise YeditException('Problem with loading yaml file. {}'.format(err))
 
         return self.yaml_dict
 
@@ -392,8 +391,8 @@ class Yedit(object):
             # AUDIT:maybe-no-member makes sense due to fuzzy types
             # pylint: disable=maybe-no-member
             if not isinstance(value, dict):
-                raise YeditException('Cannot replace key, value entry in ' +
-                                     'dict with non-dict type. value=[%s] [%s]' % (value, type(value)))  # noqa: E501
+                raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
+                                     'value=[{}] type=[{}]'.format(value, type(value)))
 
             entry.update(value)
             return (True, self.yaml_dict)
@@ -454,7 +453,17 @@ class Yedit(object):
             pass
 
         result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-        if not result:
+        if result is None:
+            return (False, self.yaml_dict)
+
+        # When path equals "" it is a special case.
+        # "" refers to the root of the document
+        # Only update the root path (entire document) when its a list or dict
+        if path == '':
+            if isinstance(result, list) or isinstance(result, dict):
+                self.yaml_dict = result
+                return (True, self.yaml_dict)
+
             return (False, self.yaml_dict)
 
         self.yaml_dict = tmp_copy
@@ -480,7 +489,7 @@ class Yedit(object):
                 pass
 
             result = Yedit.add_entry(tmp_copy, path, value, self.separator)
-            if result:
+            if result is not None:
                 self.yaml_dict = tmp_copy
                 return (True, self.yaml_dict)
 
@@ -512,112 +521,147 @@ class Yedit(object):
         # we will convert to bool if it matches any of the above cases
         if isinstance(inc_value, str) and 'bool' in vtype:
             if inc_value not in true_bools and inc_value not in false_bools:
-                raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
-                                     % (inc_value, vtype))
+                raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
         elif isinstance(inc_value, bool) and 'str' in vtype:
             inc_value = str(inc_value)
 
+        # There is a special case where '' will turn into None after yaml loading it so skip
+        if isinstance(inc_value, str) and inc_value == '':
+            pass
         # If vtype is not str then go ahead and attempt to yaml load it.
-        if isinstance(inc_value, str) and 'str' not in vtype:
+        elif isinstance(inc_value, str) and 'str' not in vtype:
             try:
-                inc_value = yaml.load(inc_value)
+                inc_value = yaml.safe_load(inc_value)
             except Exception:
-                raise YeditException('Could not determine type of incoming ' +
-                                     'value. value=[%s] vtype=[%s]'
-                                     % (type(inc_value), vtype))
+                raise YeditException('Could not determine type of incoming value. ' +
+                                     'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
 
         return inc_value
 
+    @staticmethod
+    def process_edits(edits, yamlfile):
+        '''run through a list of edits and process them one-by-one'''
+        results = []
+        for edit in edits:
+            value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
+            if edit.get('action') == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(
+                    Yedit.parse_value(edit.get('curr_value')),
+                    edit.get('curr_value_format'))
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index'),
+                                       curr_value)
+
+            elif edit.get('action') == 'append':
+                rval = yamlfile.append(edit['key'], value)
+
+            else:
+                rval = yamlfile.put(edit['key'], value)
+
+            if rval[0]:
+                results.append({'key': edit['key'], 'edit': rval[1]})
+
+        return {'changed': len(results) > 0, 'results': results}
+
     # pylint: disable=too-many-return-statements,too-many-branches
     @staticmethod
-    def run_ansible(module):
+    def run_ansible(params):
         '''perform the idempotent crud operations'''
-        yamlfile = Yedit(filename=module.params['src'],
-                         backup=module.params['backup'],
-                         separator=module.params['separator'])
+        yamlfile = Yedit(filename=params['src'],
+                         backup=params['backup'],
+                         separator=params['separator'])
+
+        state = params['state']
 
-        if module.params['src']:
+        if params['src']:
             rval = yamlfile.load()
 
-            if yamlfile.yaml_dict is None and \
-               module.params['state'] != 'present':
+            if yamlfile.yaml_dict is None and state != 'present':
                 return {'failed': True,
-                        'msg': 'Error opening file [%s].  Verify that the ' +
-                               'file exists, that it is has correct' +
-                               ' permissions, and is valid yaml.'}
-
-        if module.params['state'] == 'list':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+                        'msg': 'Error opening file [{}].  Verify that the '.format(params['src']) +
+                               'file exists, that it is has correct permissions, and is valid yaml.'}
+
+        if state == 'list':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['key']:
-                rval = yamlfile.get(module.params['key']) or {}
+            if params['key']:
+                rval = yamlfile.get(params['key']) or {}
 
-            return {'changed': False, 'result': rval, 'state': "list"}
+            return {'changed': False, 'result': rval, 'state': state}
 
-        elif module.params['state'] == 'absent':
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+        elif state == 'absent':
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
                 yamlfile.yaml_dict = content
 
-            if module.params['update']:
-                rval = yamlfile.pop(module.params['key'],
-                                    module.params['value'])
+            if params['update']:
+                rval = yamlfile.pop(params['key'], params['value'])
             else:
-                rval = yamlfile.delete(module.params['key'])
+                rval = yamlfile.delete(params['key'])
 
-            if rval[0] and module.params['src']:
+            if rval[0] and params['src']:
                 yamlfile.write()
 
-            return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
+            return {'changed': rval[0], 'result': rval[1], 'state': state}
 
-        elif module.params['state'] == 'present':
+        elif state == 'present':
             # check if content is different than what is in the file
-            if module.params['content']:
-                content = Yedit.parse_value(module.params['content'],
-                                            module.params['content_type'])
+            if params['content']:
+                content = Yedit.parse_value(params['content'], params['content_type'])
 
                 # We had no edits to make and the contents are the same
                 if yamlfile.yaml_dict == content and \
-                   module.params['value'] is None:
-                    return {'changed': False,
-                            'result': yamlfile.yaml_dict,
-                            'state': "present"}
+                   params['value'] is None:
+                    return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
 
                 yamlfile.yaml_dict = content
 
-            # we were passed a value; parse it
-            if module.params['value']:
-                value = Yedit.parse_value(module.params['value'],
-                                          module.params['value_type'])
-                key = module.params['key']
-                if module.params['update']:
-                    # pylint: disable=line-too-long
-                    curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']),  # noqa: E501
-                                                      module.params['curr_value_format'])  # noqa: E501
+            # If we were passed a key, value then
+            # we enapsulate it in a list and process it
+            # Key, Value passed to the module : Converted to Edits list #
+            edits = []
+            _edit = {}
+            if params['value'] is not None:
+                _edit['value'] = params['value']
+                _edit['value_type'] = params['value_type']
+                _edit['key'] = params['key']
 
-                    rval = yamlfile.update(key, value, module.params['index'], curr_value)  # noqa: E501
+                if params['update']:
+                    _edit['action'] = 'update'
+                    _edit['curr_value'] = params['curr_value']
+                    _edit['curr_value_format'] = params['curr_value_format']
+                    _edit['index'] = params['index']
 
-                elif module.params['append']:
-                    rval = yamlfile.append(key, value)
-                else:
-                    rval = yamlfile.put(key, value)
+                elif params['append']:
+                    _edit['action'] = 'append'
+
+                edits.append(_edit)
+
+            elif params['edits'] is not None:
+                edits = params['edits']
 
-                if rval[0] and module.params['src']:
+            if edits:
+                results = Yedit.process_edits(edits, yamlfile)
+
+                # if there were changes and a src provided to us we need to write
+                if results['changed'] and params['src']:
                     yamlfile.write()
 
-                return {'changed': rval[0],
-                        'result': rval[1], 'state': "present"}
+                return {'changed': results['changed'], 'result': results['results'], 'state': state}
 
             # no edits to make
-            if module.params['src']:
+            if params['src']:
                 # pylint: disable=redefined-variable-type
                 rval = yamlfile.write()
                 return {'changed': rval[0],
                         'result': rval[1],
-                        'state': "present"}
+                        'state': state}
 
+            # We were passed content but no src, key or value, or edits.  Return contents in memory
+            return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
         return {'failed': True, 'msg': 'Unkown state passed'}

+ 16 - 0
roles/lib_utils/src/doc/yedit

@@ -135,4 +135,20 @@ EXAMPLES = '''
 # a:
 #   b:
 #     c: d
+#
+# multiple edits at the same time
+- name: perform multiple edits
+  yedit:
+    src: somefile.yml
+    edits:
+    - key: a#b#c
+      value: d
+    - key: a#b#c#d
+      value: e
+    state: present
+# Results:
+# a:
+#   b:
+#     c:
+#       d: e
 '''

+ 0 - 52
roles/lib_utils/src/test/integration/kube-manager-test.yaml.orig

@@ -1,52 +0,0 @@
-apiVersion: v1
-kind: Pod
-metadata:
-  name: kube-controller-manager
-  namespace: kube-system
-spec:
-  hostNetwork: true
-  containers:
-  - name: kube-controller-manager
-    image: openshift/kube:v1.0.0
-    command:
-    - /hyperkube
-    - controller-manager
-    - --master=http://127.0.0.1:8080
-    - --leader-elect=true
-    - --service-account-private-key-file=/etc/kubernetes/ssl/apiserver-key.pem
-    - --root-ca-file=/etc/k8s/ssl/my.pem
-    - --my-new-parameter=openshift
-    livenessProbe:
-      httpGet:
-        host: 127.0.0.1
-        path: /healthz
-        port: 10252
-      initialDelaySeconds: 15
-      timeoutSeconds: 1
-    volumeMounts:
-    - mountPath: /etc/kubernetes/ssl
-      name: ssl-certs-kubernetes
-      readOnly: true
-    - mountPath: /etc/ssl/certs
-      name: ssl-certs-host
-      readOnly: 'true'
-  volumes:
-  - hostPath:
-      path: /etc/kubernetes/ssl
-    name: ssl-certs-kubernetes
-  - hostPath:
-      path: /usr/share/ca-certificates
-    name: ssl-certs-host
-yedittest: yedittest
-metadata-namespace: openshift-is-awesome
-nonexistingkey:
-- --my-new-parameter=openshift
-a:
-  b:
-    c: d
-e:
-  f:
-    g:
-      h:
-        i:
-          j: k

+ 30 - 1
roles/lib_utils/src/test/integration/yedit.yml

@@ -219,4 +219,33 @@
     assert:
       that: results.result == [1, 2, 3]
       msg: "Test: '[1, 2, 3]' != [{{ results.result }}]"
-###### end test create list value #####
+  ###### end test create list value #####
+
+  ###### test create multiple list value #####
+  - name: test multiple edits
+    yedit:
+      src: "{{ test_file }}"
+      edits:
+      - key: z.x.y
+        value:
+        - 1
+        - 2
+        - 3
+      - key: z.x.y
+        value: 4
+        action: append
+
+  - name: retrieve the key
+    yedit:
+      src: "{{ test_file }}"
+      state: list
+      key: z#x#y
+      separator: '#'
+    register: results
+  - debug: var=results
+
+  - name: Assert that the key was created
+    assert:
+      that: results.result == [1, 2, 3, 4]
+      msg: "Test: '[1, 2, 3, 4]' != [{{ results.result }}]"
+      ###### end test create multiple list value #####

+ 86 - 0
roles/lib_utils/src/test/unit/test_yedit.py

@@ -5,6 +5,7 @@
 import os
 import sys
 import unittest
+import mock
 
 # Removing invalid variable names for tests so that I can
 # keep them brief
@@ -277,6 +278,91 @@ class YeditTest(unittest.TestCase):
         with self.assertRaises(YeditException):
             yed.put('new.stuff.here[0]', 'item')
 
+    def test_empty_key_with_int_value(self):
+        '''test editing top level with not list or dict'''
+        yed = Yedit(content={'a': {'b': 12}})
+        result = yed.put('', 'b')
+        self.assertFalse(result[0])
+
+    def test_setting_separator(self):
+        '''test editing top level with not list or dict'''
+        yed = Yedit(content={'a': {'b': 12}})
+        yed.separator = ':'
+        self.assertEqual(yed.separator, ':')
+
+    def test_remove_all(self):
+        '''test removing all data'''
+        data = Yedit.remove_entry({'a': {'b': 12}}, '')
+        self.assertTrue(data)
+
+    def test_remove_list_entry(self):
+        '''test removing list entry'''
+        data = {'a': {'b': [{'c': 3}]}}
+        results = Yedit.remove_entry(data, 'a.b[0]')
+        self.assertTrue(results)
+        self.assertTrue(data, {'a': {'b': []}})
+
+    def test_parse_value_string_true(self):
+        '''test parse_value'''
+        results = Yedit.parse_value('true', 'str')
+        self.assertEqual(results, 'true')
+
+    def test_parse_value_bool_true(self):
+        '''test parse_value'''
+        results = Yedit.parse_value('true', 'bool')
+        self.assertTrue(results)
+
+    def test_parse_value_bool_exception(self):
+        '''test parse_value'''
+        with self.assertRaises(YeditException):
+            Yedit.parse_value('TTT', 'bool')
+
+    @mock.patch('yedit.Yedit.write')
+    def test_run_ansible_basic(self, mock_write):
+        '''test parse_value'''
+        params = {
+            'src': None,
+            'backup': False,
+            'separator': '.',
+            'state': 'present',
+            'edits': [],
+            'value': None,
+            'key': None,
+            'content': {'a': {'b': {'c': 1}}},
+            'content_type': '',
+        }
+
+        results = Yedit.run_ansible(params)
+
+        mock_write.side_effect = [
+            (True, params['content']),
+        ]
+
+        self.assertFalse(results['changed'])
+
+    @mock.patch('yedit.Yedit.write')
+    def test_run_ansible_and_write(self, mock_write):
+        '''test parse_value'''
+        params = {
+            'src': '/tmp/test',
+            'backup': False,
+            'separator': '.',
+            'state': 'present',
+            'edits': [],
+            'value': None,
+            'key': None,
+            'content': {'a': {'b': {'c': 1}}},
+            'content_type': '',
+        }
+
+        results = Yedit.run_ansible(params)
+
+        mock_write.side_effect = [
+            (True, params['content']),
+        ]
+
+        self.assertTrue(results['changed'])
+
     def tearDown(self):
         '''TearDown method'''
         os.unlink(YeditTest.filename)