Browse Source

Added the ability to do multiple edits

Kenny Woodson 8 years ago
parent
commit
a69a84339e

+ 106 - 55
roles/lib_utils/library/yedit.py

@@ -180,6 +180,22 @@ 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 -*- -*- -*-
@@ -698,13 +714,15 @@ 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=[%s] vtype=[%s]' % (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)
             except Exception:
@@ -714,97 +732,129 @@ class Yedit(object):
 
         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 'action' in edit and edit['action'] == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(Yedit.parse_value(edit.get('curr_value', None)),  # noqa: E501
+                                                  edit.get('curr_value_format', None))  # noqa: E501
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index', None),
+                                       curr_value)
+
+            elif 'action' in edit and edit['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'])
+        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}
 
         return {'failed': True, 'msg': 'Unkown state passed'}
 
@@ -837,12 +887,13 @@ 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)
+    rval = Yedit.run_ansible(module.params)
     if 'failed' in rval and rval['failed']:
         module.fail_json(**rval)
 

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

@@ -26,12 +26,13 @@ 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)
+    rval = Yedit.run_ansible(module.params)
     if 'failed' in rval and rval['failed']:
         module.fail_json(**rval)
 

+ 88 - 54
roles/lib_utils/src/class/yedit.py

@@ -512,13 +512,15 @@ 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=[%s] vtype=[%s]' % (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)
             except Exception:
@@ -528,96 +530,128 @@ class Yedit(object):
 
         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 'action' in edit and edit['action'] == 'update':
+                # pylint: disable=line-too-long
+                curr_value = Yedit.get_curr_value(Yedit.parse_value(edit.get('curr_value', None)),  # noqa: E501
+                                                  edit.get('curr_value_format', None))  # noqa: E501
+
+                rval = yamlfile.update(edit['key'],
+                                       value,
+                                       edit.get('index', None),
+                                       curr_value)
+
+            elif 'action' in edit and edit['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'])
+        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}
 
         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
 '''

+ 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 #####