summaryrefslogtreecommitdiffstats
path: root/roles/lib_yaml_editor
diff options
context:
space:
mode:
Diffstat (limited to 'roles/lib_yaml_editor')
-rw-r--r--roles/lib_yaml_editor/build/ansible/yedit.py66
-rwxr-xr-xroles/lib_yaml_editor/build/generate.py43
-rw-r--r--roles/lib_yaml_editor/build/src/base.py17
-rw-r--r--roles/lib_yaml_editor/build/src/yedit.py201
-rw-r--r--roles/lib_yaml_editor/build/test/foo.yml1
-rwxr-xr-xroles/lib_yaml_editor/build/test/test.yaml15
-rw-r--r--roles/lib_yaml_editor/library/yedit.py183
7 files changed, 469 insertions, 57 deletions
diff --git a/roles/lib_yaml_editor/build/ansible/yedit.py b/roles/lib_yaml_editor/build/ansible/yedit.py
new file mode 100644
index 000000000..a4c0d40b3
--- /dev/null
+++ b/roles/lib_yaml_editor/build/ansible/yedit.py
@@ -0,0 +1,66 @@
+#pylint: skip-file
+
+def main():
+ '''
+ ansible oc module for secrets
+ '''
+
+ module = AnsibleModule(
+ argument_spec=dict(
+ state=dict(default='present', type='str',
+ choices=['present', 'absent', 'list']),
+ debug=dict(default=False, type='bool'),
+ src=dict(default=None, type='str'),
+ content=dict(default=None, type='dict'),
+ key=dict(default=None, type='str'),
+ value=dict(default=None, type='str'),
+ value_format=dict(default='yaml', choices=['yaml', 'json'], type='str'),
+ ),
+ #mutually_exclusive=[["src", "content"]],
+
+ supports_check_mode=True,
+ )
+ state = module.params['state']
+
+ yamlfile = Yedit(module.params['src'], module.params['content'])
+
+ rval = yamlfile.load()
+ if not rval and state != 'present':
+ module.fail_json(msg='Error opening file [%s]. Verify that the' + \
+ ' file exists, that it is has correct permissions, and is valid yaml.')
+
+ if state == 'list':
+ module.exit_json(changed=False, results=rval, state="list")
+
+ if state == 'absent':
+ rval = yamlfile.delete(module.params['key'])
+ module.exit_json(changed=rval[0], results=rval[1], state="absent")
+
+ if state == 'present':
+
+ if module.params['value_format'] == 'yaml':
+ value = yaml.load(module.params['value'])
+ elif module.params['value_format'] == 'json':
+ value = json.loads(module.params['value'])
+
+ if rval:
+ rval = yamlfile.put(module.params['key'], value)
+ module.exit_json(changed=rval[0], results=rval[1], state="present")
+
+ if not module.params['content']:
+ rval = yamlfile.create(module.params['key'], value)
+ else:
+ yamlfile.write()
+ rval = yamlfile.load()
+ module.exit_json(changed=rval[0], results=rval[1], state="present")
+
+ module.exit_json(failed=True,
+ changed=False,
+ results='Unknown state passed. %s' % state,
+ state="unknown")
+
+# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, locally-disabled
+# import module snippets. This are required
+from ansible.module_utils.basic import *
+
+main()
diff --git a/roles/lib_yaml_editor/build/generate.py b/roles/lib_yaml_editor/build/generate.py
new file mode 100755
index 000000000..312e4d0ee
--- /dev/null
+++ b/roles/lib_yaml_editor/build/generate.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+'''
+ Generate the openshift-ansible/roles/lib_openshift_cli/library/ modules.
+'''
+
+import os
+
+# pylint: disable=anomalous-backslash-in-string
+GEN_STR = "#!/usr/bin/env python\n" + \
+ "# ___ ___ _ _ ___ ___ _ _____ ___ ___\n" + \
+ "# / __| __| \| | __| _ \ /_\_ _| __| \\\n" + \
+ "# | (_ | _|| .` | _|| / / _ \| | | _|| |) |\n" + \
+ "# \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____\n" + \
+ "# | \ / _ \ | \| |/ _ \_ _| | __| \_ _|_ _|\n" + \
+ "# | |) | (_) | | .` | (_) || | | _|| |) | | | |\n" + \
+ "# |___/ \___/ |_|\_|\___/ |_| |___|___/___| |_|\n"
+
+OPENSHIFT_ANSIBLE_PATH = os.path.dirname(os.path.realpath(__file__))
+
+FILES = {'yedit.py': ['src/base.py', 'src/yedit.py', 'ansible/yedit.py'],
+ }
+
+def main():
+ ''' combine the necessary files to create the ansible module '''
+ library = os.path.join(OPENSHIFT_ANSIBLE_PATH, '..', 'library/')
+ for fname, parts in FILES.items():
+ with open(os.path.join(library, fname), 'w') as afd:
+ afd.seek(0)
+ afd.write(GEN_STR)
+ for fpart in parts:
+ with open(os.path.join(OPENSHIFT_ANSIBLE_PATH, fpart)) as pfd:
+ # first line is pylint disable so skip it
+ for idx, line in enumerate(pfd):
+ if idx == 0 and 'skip-file' in line:
+ continue
+
+ afd.write(line)
+
+
+if __name__ == '__main__':
+ main()
+
+
diff --git a/roles/lib_yaml_editor/build/src/base.py b/roles/lib_yaml_editor/build/src/base.py
new file mode 100644
index 000000000..9e43d45dc
--- /dev/null
+++ b/roles/lib_yaml_editor/build/src/base.py
@@ -0,0 +1,17 @@
+# pylint: skip-file
+
+'''
+module for managing yaml files
+'''
+
+import os
+import re
+
+import yaml
+# This is here because of a bug that causes yaml
+# to incorrectly handle timezone info on timestamps
+def timestamp_constructor(_, node):
+ ''' return timestamps as strings'''
+ return str(node.value)
+yaml.add_constructor(u'tag:yaml.org,2002:timestamp', timestamp_constructor)
+
diff --git a/roles/lib_yaml_editor/build/src/yedit.py b/roles/lib_yaml_editor/build/src/yedit.py
new file mode 100644
index 000000000..faef577ae
--- /dev/null
+++ b/roles/lib_yaml_editor/build/src/yedit.py
@@ -0,0 +1,201 @@
+# pylint: skip-file
+
+class YeditException(Exception):
+ ''' Exception class for Yedit '''
+ pass
+
+class Yedit(object):
+ ''' Class to modify yaml files '''
+ re_valid_key = r"(((\[-?\d+\])|(\w+)).?)+$"
+ re_key = r"(?:\[(-?\d+)\])|(\w+)"
+
+ def __init__(self, filename=None, content=None, content_type='yaml'):
+ self.content = content
+ self.filename = filename
+ self.__yaml_dict = content
+ self.content_type = content_type
+ if self.filename and not self.content:
+ self.load(content_type=self.content_type)
+
+ @property
+ def yaml_dict(self):
+ ''' getter method for yaml_dict '''
+ return self.__yaml_dict
+
+ @yaml_dict.setter
+ def yaml_dict(self, value):
+ ''' setter method for yaml_dict '''
+ self.__yaml_dict = value
+
+ @staticmethod
+ def remove_entry(data, key):
+ ''' remove data at location key '''
+ if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))):
+ return None
+
+ key_indexes = re.findall(Yedit.re_key, key)
+ for arr_ind, dict_key in key_indexes[:-1]:
+ if dict_key and isinstance(data, dict):
+ data = data.get(dict_key, None)
+ elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1:
+ data = data[int(arr_ind)]
+ else:
+ return None
+
+ # process last index for remove
+ # expected list entry
+ if key_indexes[-1][0]:
+ if isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1:
+ del data[int(key_indexes[-1][0])]
+
+ # expected dict entry
+ elif key_indexes[-1][1]:
+ if isinstance(data, dict):
+ del data[key_indexes[-1][1]]
+
+ @staticmethod
+ def add_entry(data, key, item=None):
+ ''' Get an item from a dictionary with key notation a.b.c
+ d = {'a': {'b': 'c'}}}
+ key = a.b
+ return c
+ '''
+ if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))):
+ return None
+
+ curr_data = data
+
+ key_indexes = re.findall(Yedit.re_key, key)
+ for arr_ind, dict_key in key_indexes[:-1]:
+ if dict_key:
+ if isinstance(data, dict) and data.has_key(dict_key):
+ data = data[dict_key]
+ continue
+
+ data[dict_key] = {}
+ data = data[dict_key]
+
+ elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1:
+ data = data[int(arr_ind)]
+ else:
+ return None
+
+ # process last index for add
+ # expected list entry
+ if key_indexes[-1][0] and isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1:
+ data[int(key_indexes[-1][0])] = item
+
+ # expected dict entry
+ elif key_indexes[-1][1] and isinstance(data, dict):
+ data[key_indexes[-1][1]] = item
+
+ return curr_data
+
+ @staticmethod
+ def get_entry(data, key):
+ ''' Get an item from a dictionary with key notation a.b.c
+ d = {'a': {'b': 'c'}}}
+ key = a.b
+ return c
+ '''
+ if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))):
+ return None
+
+ key_indexes = re.findall(Yedit.re_key, key)
+ for arr_ind, dict_key in key_indexes:
+ if dict_key and isinstance(data, dict):
+ data = data.get(dict_key, None)
+ elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1:
+ data = data[int(arr_ind)]
+ else:
+ return None
+
+ return data
+
+ def write(self):
+ ''' write to file '''
+ if not self.filename:
+ raise YeditException('Please specify a filename.')
+
+ with open(self.filename, 'w') as yfd:
+ yfd.write(yaml.safe_dump(self.yaml_dict, default_flow_style=False))
+
+ def read(self):
+ ''' write to file '''
+ # check if it exists
+ if not self.exists():
+ return None
+
+ contents = None
+ with open(self.filename) as yfd:
+ contents = yfd.read()
+
+ return contents
+
+ def exists(self):
+ ''' return whether file exists '''
+ if os.path.exists(self.filename):
+ return True
+
+ return False
+
+ def load(self, content_type='yaml'):
+ ''' return yaml file '''
+ contents = self.read()
+
+ if not contents:
+ return None
+
+ # check if it is yaml
+ try:
+ if content_type == 'yaml':
+ self.yaml_dict = yaml.load(contents)
+ elif content_type == 'json':
+ self.yaml_dict = json.loads(contents)
+ except yaml.YAMLError as _:
+ # Error loading yaml or json
+ return None
+
+ return self.yaml_dict
+
+ def get(self, key):
+ ''' get a specified key'''
+ try:
+ entry = Yedit.get_entry(self.yaml_dict, key)
+ except KeyError as _:
+ entry = None
+
+ return entry
+
+ def delete(self, key):
+ ''' put key, value into a yaml file '''
+ try:
+ entry = Yedit.get_entry(self.yaml_dict, key)
+ except KeyError as _:
+ entry = None
+ if not entry:
+ return (False, self.yaml_dict)
+
+ Yedit.remove_entry(self.yaml_dict, key)
+ return (True, self.yaml_dict)
+
+ def put(self, key, value):
+ ''' put key, value into a yaml file '''
+ try:
+ entry = Yedit.get_entry(self.yaml_dict, key)
+ except KeyError as _:
+ entry = None
+
+ if entry == value:
+ return (False, self.yaml_dict)
+
+ Yedit.add_entry(self.yaml_dict, key, value)
+ return (True, self.yaml_dict)
+
+ def create(self, key, value):
+ ''' create the file '''
+ if not self.exists():
+ self.yaml_dict = {key: value}
+ return (True, self.yaml_dict)
+
+ return (False, self.yaml_dict)
diff --git a/roles/lib_yaml_editor/build/test/foo.yml b/roles/lib_yaml_editor/build/test/foo.yml
new file mode 100644
index 000000000..20e9ff3fe
--- /dev/null
+++ b/roles/lib_yaml_editor/build/test/foo.yml
@@ -0,0 +1 @@
+foo: bar
diff --git a/roles/lib_yaml_editor/build/test/test.yaml b/roles/lib_yaml_editor/build/test/test.yaml
new file mode 100755
index 000000000..ac9c37565
--- /dev/null
+++ b/roles/lib_yaml_editor/build/test/test.yaml
@@ -0,0 +1,15 @@
+#!/usr/bin/ansible-playbook
+---
+- hosts: localhost
+ gather_facts: no
+ tasks:
+ - yedit:
+ src: /home/kwoodson/git/openshift-ansible/roles/lib_yaml_editor/build/test/foo.yml
+ key: foo
+ value: barplus
+ state: present
+ register: output
+
+ - debug:
+ msg: "{{ output }}"
+
diff --git a/roles/lib_yaml_editor/library/yedit.py b/roles/lib_yaml_editor/library/yedit.py
index b75aff109..696ece63b 100644
--- a/roles/lib_yaml_editor/library/yedit.py
+++ b/roles/lib_yaml_editor/library/yedit.py
@@ -1,10 +1,27 @@
#!/usr/bin/env python
+# ___ ___ _ _ ___ ___ _ _____ ___ ___
+# / __| __| \| | __| _ \ /_\_ _| __| \
+# | (_ | _|| .` | _|| / / _ \| | | _|| |) |
+# \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____
+# | \ / _ \ | \| |/ _ \_ _| | __| \_ _|_ _|
+# | |) | (_) | | .` | (_) || | | _|| |) | | | |
+# |___/ \___/ |_|\_|\___/ |_| |___|___/___| |_|
+
'''
-module for openshift cloud secrets
+module for managing yaml files
'''
import os
+import re
+
import yaml
+# This is here because of a bug that causes yaml
+# to incorrectly handle timezone info on timestamps
+def timestamp_constructor(_, node):
+ ''' return timestamps as strings'''
+ return str(node.value)
+yaml.add_constructor(u'tag:yaml.org,2002:timestamp', timestamp_constructor)
+
class YeditException(Exception):
''' Exception class for Yedit '''
@@ -12,11 +29,16 @@ class YeditException(Exception):
class Yedit(object):
''' Class to modify yaml files '''
+ re_valid_key = r"(((\[-?\d+\])|(\w+)).?)+$"
+ re_key = r"(?:\[(-?\d+)\])|(\w+)"
- def __init__(self, filename):
+ def __init__(self, filename=None, content=None, content_type='yaml'):
+ self.content = content
self.filename = filename
- self.__yaml_dict = None
- self.get()
+ self.__yaml_dict = content
+ self.content_type = content_type
+ if self.filename and not self.content:
+ self.load(content_type=self.content_type)
@property
def yaml_dict(self):
@@ -29,63 +51,97 @@ class Yedit(object):
self.__yaml_dict = value
@staticmethod
- def remove_entry(data, keys):
- ''' remove an item from a dictionary with key notation a.b.c
- d = {'a': {'b': 'c'}}}
- keys = a.b
- item = c
- '''
- if "." in keys:
- key, rest = keys.split(".", 1)
- if key in data.keys():
- Yedit.remove_entry(data[key], rest)
- else:
- del data[keys]
+ def remove_entry(data, key):
+ ''' remove data at location key '''
+ if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))):
+ return None
+
+ key_indexes = re.findall(Yedit.re_key, key)
+ for arr_ind, dict_key in key_indexes[:-1]:
+ if dict_key and isinstance(data, dict):
+ data = data.get(dict_key, None)
+ elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1:
+ data = data[int(arr_ind)]
+ else:
+ return None
+
+ # process last index for remove
+ # expected list entry
+ if key_indexes[-1][0]:
+ if isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1:
+ del data[int(key_indexes[-1][0])]
+
+ # expected dict entry
+ elif key_indexes[-1][1]:
+ if isinstance(data, dict):
+ del data[key_indexes[-1][1]]
@staticmethod
- def add_entry(data, keys, item):
- ''' Add an item to a dictionary with key notation a.b.c
+ def add_entry(data, key, item=None):
+ ''' Get an item from a dictionary with key notation a.b.c
d = {'a': {'b': 'c'}}}
- keys = a.b
- item = c
+ key = a.b
+ return c
'''
- if "." in keys:
- key, rest = keys.split(".", 1)
- if key not in data:
- data[key] = {}
+ if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))):
+ return None
+
+ curr_data = data
+
+ key_indexes = re.findall(Yedit.re_key, key)
+ for arr_ind, dict_key in key_indexes[:-1]:
+ if dict_key:
+ if isinstance(data, dict) and data.has_key(dict_key):
+ data = data[dict_key]
+ continue
+
+ data[dict_key] = {}
+ data = data[dict_key]
- if not isinstance(data, dict):
- raise YeditException('Invalid add_entry called on a [%s] of type [%s].' % (data, type(data)))
+ elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1:
+ data = data[int(arr_ind)]
else:
- Yedit.add_entry(data[key], rest, item)
+ return None
- else:
- data[keys] = item
+ # process last index for add
+ # expected list entry
+ if key_indexes[-1][0] and isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1:
+ data[int(key_indexes[-1][0])] = item
+
+ # expected dict entry
+ elif key_indexes[-1][1] and isinstance(data, dict):
+ data[key_indexes[-1][1]] = item
+ return curr_data
@staticmethod
- def get_entry(data, keys):
+ def get_entry(data, key):
''' Get an item from a dictionary with key notation a.b.c
d = {'a': {'b': 'c'}}}
- keys = a.b
+ key = a.b
return c
'''
- if keys and "." in keys:
- key, rest = keys.split(".", 1)
- if not isinstance(data[key], dict):
- raise YeditException('Invalid get_entry called on a [%s] of type [%s].' % (data, type(data)))
+ if not (key and re.match(Yedit.re_valid_key, key) and isinstance(data, (list, dict))):
+ return None
+ key_indexes = re.findall(Yedit.re_key, key)
+ for arr_ind, dict_key in key_indexes:
+ if dict_key and isinstance(data, dict):
+ data = data.get(dict_key, None)
+ elif arr_ind and isinstance(data, list) and int(arr_ind) <= len(data) - 1:
+ data = data[int(arr_ind)]
else:
- return Yedit.get_entry(data[key], rest)
-
- else:
- return data.get(keys, None)
+ return None
+ return data
def write(self):
''' write to file '''
+ if not self.filename:
+ raise YeditException('Please specify a filename.')
+
with open(self.filename, 'w') as yfd:
- yfd.write(yaml.dump(self.yaml_dict, default_flow_style=False))
+ yfd.write(yaml.safe_dump(self.yaml_dict, default_flow_style=False))
def read(self):
''' write to file '''
@@ -105,7 +161,8 @@ class Yedit(object):
return True
return False
- def get(self):
+
+ def load(self, content_type='yaml'):
''' return yaml file '''
contents = self.read()
@@ -114,13 +171,25 @@ class Yedit(object):
# check if it is yaml
try:
- self.yaml_dict = yaml.load(contents)
+ if content_type == 'yaml':
+ self.yaml_dict = yaml.load(contents)
+ elif content_type == 'json':
+ self.yaml_dict = json.loads(contents)
except yaml.YAMLError as _:
- # Error loading yaml
+ # Error loading yaml or json
return None
return self.yaml_dict
+ def get(self, key):
+ ''' get a specified key'''
+ try:
+ entry = Yedit.get_entry(self.yaml_dict, key)
+ except KeyError as _:
+ entry = None
+
+ return entry
+
def delete(self, key):
''' put key, value into a yaml file '''
try:
@@ -131,8 +200,7 @@ class Yedit(object):
return (False, self.yaml_dict)
Yedit.remove_entry(self.yaml_dict, key)
- self.write()
- return (True, self.get())
+ return (True, self.yaml_dict)
def put(self, key, value):
''' put key, value into a yaml file '''
@@ -145,18 +213,15 @@ class Yedit(object):
return (False, self.yaml_dict)
Yedit.add_entry(self.yaml_dict, key, value)
- self.write()
- return (True, self.get())
+ return (True, self.yaml_dict)
def create(self, key, value):
''' create the file '''
if not self.exists():
self.yaml_dict = {key: value}
- self.write()
- return (True, self.get())
-
- return (False, self.get())
+ return (True, self.yaml_dict)
+ return (False, self.yaml_dict)
def main():
'''
@@ -168,24 +233,24 @@ def main():
state=dict(default='present', type='str',
choices=['present', 'absent', 'list']),
debug=dict(default=False, type='bool'),
-
src=dict(default=None, type='str'),
+ content=dict(default=None, type='dict'),
key=dict(default=None, type='str'),
value=dict(default=None, type='str'),
value_format=dict(default='yaml', choices=['yaml', 'json'], type='str'),
),
- mutually_exclusive=[["contents", "files"]],
+ #mutually_exclusive=[["src", "content"]],
supports_check_mode=True,
)
state = module.params['state']
- yamlfile = Yedit(module.params['src'])
+ yamlfile = Yedit(module.params['src'], module.params['content'])
- rval = yamlfile.get()
+ rval = yamlfile.load()
if not rval and state != 'present':
module.fail_json(msg='Error opening file [%s]. Verify that the' + \
- ' file exists and that it is has correct permissions.')
+ ' file exists, that it is has correct permissions, and is valid yaml.')
if state == 'list':
module.exit_json(changed=False, results=rval, state="list")
@@ -205,7 +270,11 @@ def main():
rval = yamlfile.put(module.params['key'], value)
module.exit_json(changed=rval[0], results=rval[1], state="present")
- rval = yamlfile.create(module.params['key'], value)
+ if not module.params['content']:
+ rval = yamlfile.create(module.params['key'], value)
+ else:
+ yamlfile.write()
+ rval = yamlfile.load()
module.exit_json(changed=rval[0], results=rval[1], state="present")
module.exit_json(failed=True,