summaryrefslogtreecommitdiffstats
path: root/roles/lib_openshift_api/library/oc_secret.py
diff options
context:
space:
mode:
Diffstat (limited to 'roles/lib_openshift_api/library/oc_secret.py')
-rw-r--r--roles/lib_openshift_api/library/oc_secret.py394
1 files changed, 341 insertions, 53 deletions
diff --git a/roles/lib_openshift_api/library/oc_secret.py b/roles/lib_openshift_api/library/oc_secret.py
index d69d490ac..a03022e35 100644
--- a/roles/lib_openshift_api/library/oc_secret.py
+++ b/roles/lib_openshift_api/library/oc_secret.py
@@ -1,16 +1,30 @@
#!/usr/bin/env python
+# ___ ___ _ _ ___ ___ _ _____ ___ ___
+# / __| __| \| | __| _ \ /_\_ _| __| \
+# | (_ | _|| .` | _|| / / _ \| | | _|| |) |
+# \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____
+# | \ / _ \ | \| |/ _ \_ _| | __| \_ _|_ _|
+# | |) | (_) | | .` | (_) || | | _|| |) | | | |
+# |___/ \___/ |_|\_|\___/ |_| |___|___/___| |_|
'''
- OpenShiftCLI class that wraps the oc commands in a subprocess
+ OpenShiftCLI class that wraps the oc commands in a subprocess
'''
+
import atexit
import json
import os
import shutil
import subprocess
+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)
-# The base class is here to share methods.
-# Currently there is only 1 but will grow in the future.
# pylint: disable=too-few-public-methods
class OpenShiftCLI(object):
''' Class to wrap the oc command line tools '''
@@ -23,13 +37,69 @@ class OpenShiftCLI(object):
self.verbose = verbose
self.kubeconfig = kubeconfig
+ # Pylint allows only 5 arguments to be passed.
+ # pylint: disable=too-many-arguments
+ def _replace_content(self, resource, rname, content, force=False):
+ ''' replace the current object with the content '''
+ res = self._get(resource, rname)
+ if not res['results']:
+ return res
+
+ fname = '/tmp/%s' % rname
+ yed = Yedit(fname, res['results'][0])
+ changes = []
+ for key, value in content.items():
+ changes.append(yed.put(key, value))
+
+ if any([not change[0] for change in changes]):
+ return {'returncode': 0, 'updated': False}
+
+ yed.write()
+
+ atexit.register(Utils.cleanup, [fname])
+
+ return self._replace(fname, force)
+
+ def _replace(self, fname, force=False):
+ '''return all pods '''
+ cmd = ['-n', self.namespace, 'replace', '-f', fname]
+ if force:
+ cmd.append('--force')
+ return self.oc_cmd(cmd)
+
+ def _create(self, fname):
+ '''return all pods '''
+ return self.oc_cmd(['create', '-f', fname, '-n', self.namespace])
+
+ def _delete(self, resource, rname):
+ '''return all pods '''
+ return self.oc_cmd(['delete', resource, rname, '-n', self.namespace])
+
+ def _get(self, resource, rname=None):
+ '''return a secret by name '''
+ cmd = ['get', resource, '-o', 'json', '-n', self.namespace]
+ if rname:
+ cmd.append(rname)
+
+ rval = self.oc_cmd(cmd, output=True)
+
+ # Ensure results are retuned in an array
+ if rval.has_key('items'):
+ rval['results'] = rval['items']
+ elif not isinstance(rval['results'], list):
+ rval['results'] = [rval['results']]
+
+ return rval
+
def oc_cmd(self, cmd, output=False):
'''Base command for oc '''
#cmds = ['/usr/bin/oc', '--config', self.kubeconfig]
cmds = ['/usr/bin/oc']
cmds.extend(cmd)
+ rval = {}
results = ''
+ err = None
if self.verbose:
print ' '.join(cmds)
@@ -38,46 +108,73 @@ class OpenShiftCLI(object):
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env={'KUBECONFIG': self.kubeconfig})
+
proc.wait()
+ stdout = proc.stdout.read()
+ stderr = proc.stderr.read()
+
+ rval = {"returncode": proc.returncode,
+ "results": results,
+ }
+
if proc.returncode == 0:
if output:
try:
- results = json.loads(proc.stdout.read())
+ rval['results'] = json.loads(stdout)
except ValueError as err:
if "No JSON object could be decoded" in err.message:
- results = err.message
+ err = err.message
if self.verbose:
- print proc.stderr.read()
- print results
+ print stdout
+ print stderr
print
- return {"returncode": proc.returncode, "results": results}
+ if err:
+ rval.update({"err": err,
+ "stderr": stderr,
+ "stdout": stdout,
+ "cmd": cmds
+ })
- return {"returncode": proc.returncode,
- "stderr": proc.stderr.read(),
- "stdout": proc.stdout.read(),
- "results": {}
- }
+ else:
+ rval.update({"stderr": stderr,
+ "stdout": stdout,
+ "results": {},
+ })
+
+ return rval
class Utils(object):
''' utilities for openshiftcli modules '''
@staticmethod
+ def create_file(rname, data, ftype=None):
+ ''' create a file in tmp with name and contents'''
+ path = os.path.join('/tmp', rname)
+ with open(path, 'w') as fds:
+ if ftype == 'yaml':
+ fds.write(yaml.safe_dump(data, default_flow_style=False))
+
+ elif ftype == 'json':
+ fds.write(json.dumps(data))
+ else:
+ fds.write(data)
+
+ # Register cleanup when module is done
+ atexit.register(Utils.cleanup, [path])
+ return path
+
+ @staticmethod
def create_files_from_contents(data):
'''Turn an array of dict: filename, content into a files array'''
files = []
for sfile in data:
- path = os.path.join('/tmp', sfile['path'])
- with open(path, 'w') as fds:
- fds.write(sfile['content'])
+ path = Utils.create_file(sfile['path'], sfile['content'])
files.append(path)
- # Register cleanup when module is done
- atexit.register(Utils.cleanup, files)
return files
-
@staticmethod
def cleanup(files):
'''Clean up on exit '''
@@ -106,7 +203,6 @@ class Utils(object):
''' Find the specified result by name'''
rval = None
for result in results:
- #print "%s == %s" % (result['metadata']['name'], name)
if result.has_key('metadata') and result['metadata']['name'] == _name:
rval = result
break
@@ -121,7 +217,7 @@ class Utils(object):
contents = sfd.read()
if sfile_type == 'yaml':
- contents = yaml.load(contents)
+ contents = yaml.safe_load(contents)
elif sfile_type == 'json':
contents = json.loads(contents)
@@ -134,7 +230,7 @@ class Utils(object):
''' Given a user defined definition, compare it with the results given back by our query. '''
# Currently these values are autogenerated and we do not need to check them
- skip = ['creationTimestamp', 'selfLink', 'resourceVersion', 'uid', 'namespace']
+ skip = ['metadata', 'status']
for key, value in result_def.items():
if key in skip:
@@ -166,7 +262,7 @@ class Utils(object):
print "keys are not equal in dict"
return False
- result = Utils.check_def_equal(user_def[key], value)
+ result = Utils.check_def_equal(user_def[key], value, debug=debug)
if not result:
if debug:
print "dict returned false"
@@ -183,6 +279,214 @@ class Utils(object):
return True
+class YeditException(Exception):
+ ''' Exception class for Yedit '''
+ pass
+
+class Yedit(object):
+ ''' Class to modify yaml files '''
+ re_valid_key = r"(((\[-?\d+\])|([a-zA-Z-./]+)).?)+$"
+ re_key = r"(?:\[(-?\d+)\])|([a-zA-Z-./]+)"
+
+ 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])]
+ return True
+
+ # expected dict entry
+ elif key_indexes[-1][1]:
+ if isinstance(data, dict):
+ del data[key_indexes[-1][1]]
+ return True
+
+ @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):
+ ''' remove key from a dict'''
+ try:
+ entry = Yedit.get_entry(self.yaml_dict, key)
+ except KeyError as _:
+ entry = None
+ if not entry:
+ return (False, self.yaml_dict)
+
+ result = Yedit.remove_entry(self.yaml_dict, key)
+ if not result:
+ return (False, self.yaml_dict)
+
+ return (True, self.yaml_dict)
+
+ def put(self, key, value):
+ ''' put key, value into a dict '''
+ try:
+ entry = Yedit.get_entry(self.yaml_dict, key)
+ except KeyError as _:
+ entry = None
+
+ if entry == value:
+ return (False, self.yaml_dict)
+
+ result = Yedit.add_entry(self.yaml_dict, key, value)
+ if not result:
+ return (False, self.yaml_dict)
+
+ return (True, self.yaml_dict)
+
+ def create(self, key, value):
+ ''' create a yaml file '''
+ if not self.exists():
+ self.yaml_dict = {key: value}
+ return (True, self.yaml_dict)
+
+ return (False, self.yaml_dict)
+
class Secret(OpenShiftCLI):
''' Class to wrap the oc command line tools
'''
@@ -192,34 +496,22 @@ class Secret(OpenShiftCLI):
kubeconfig='/etc/origin/master/admin.kubeconfig',
verbose=False):
''' Constructor for OpenshiftOC '''
- super(Secret, OpenShiftCLI).__init__(namespace, kubeconfig)
+ super(Secret, self).__init__(namespace, kubeconfig)
self.namespace = namespace
self.name = secret_name
self.kubeconfig = kubeconfig
self.verbose = verbose
- def get_secrets(self):
+ def get(self):
'''return a secret by name '''
- cmd = ['get', 'secrets', '-o', 'json', '-n', self.namespace]
- if self.name:
- cmd.append(self.name)
-
- rval = self.oc_cmd(cmd, output=True)
-
- # Ensure results are retuned in an array
- if rval.has_key('items'):
- rval['results'] = rval['items']
- elif not isinstance(rval['results'], list):
- rval['results'] = [rval['results']]
+ return self._get('secrets', self.name)
- return rval
+ def delete(self):
+ '''delete a secret by name'''
+ return self._delete('secrets', self.name)
- def delete_secret(self):
- '''return all pods '''
- return self.oc_cmd(['delete', 'secrets', self.name, '-n', self.namespace])
-
- def secret_new(self, files=None, contents=None):
- '''Create a secret with all pods '''
+ def create(self, files=None, contents=None):
+ '''Create a secret '''
if not files:
files = Utils.create_files_from_contents(contents)
@@ -229,7 +521,7 @@ class Secret(OpenShiftCLI):
return self.oc_cmd(cmd)
- def update_secret(self, files, force=False):
+ def update(self, files, force=False):
'''run update secret
This receives a list of file names and converts it into a secret.
@@ -243,13 +535,9 @@ class Secret(OpenShiftCLI):
with open(sfile_path, 'w') as sfd:
sfd.write(json.dumps(secret['results']))
- cmd = ['replace', '-f', sfile_path]
- if force:
- cmd = ['replace', '--force', '-f', sfile_path]
-
atexit.register(Utils.cleanup, [sfile_path])
- return self.oc_cmd(cmd)
+ return self._replace(sfile_path, force=force)
def prep_secret(self, files=None, contents=None):
''' return what the secret would look like if created
@@ -296,7 +584,7 @@ def main():
state = module.params['state']
- api_rval = occmd.get_secrets()
+ api_rval = occmd.get()
#####
# Get
@@ -316,7 +604,7 @@ def main():
if module.check_mode:
module.exit_json(change=False, msg='Would have performed a delete.')
- api_rval = occmd.delete_secret()
+ api_rval = occmd.delete()
module.exit_json(changed=True, results=api_rval, state="absent")
@@ -336,7 +624,7 @@ def main():
if module.check_mode:
module.exit_json(change=False, msg='Would have performed a create.')
- api_rval = occmd.secret_new(module.params['files'], module.params['contents'])
+ api_rval = occmd.create(module.params['files'], module.params['contents'])
# Remove files
if files and module.params['delete_after']:
@@ -363,7 +651,7 @@ def main():
if module.check_mode:
module.exit_json(change=False, msg='Would have performed an update.')
- api_rval = occmd.update_secret(files, force=module.params['force'])
+ api_rval = occmd.update(files, force=module.params['force'])
# Remove files
if secret and module.params['delete_after']: