diff options
-rw-r--r-- | roles/lib_openshift/library/oc_project.py | 172 | ||||
-rw-r--r-- | roles/lib_openshift/src/class/oc_project.py | 6 | ||||
-rw-r--r-- | roles/lib_openshift/src/lib/project.py | 15 |
3 files changed, 144 insertions, 49 deletions
diff --git a/roles/lib_openshift/library/oc_project.py b/roles/lib_openshift/library/oc_project.py index db3865f8b..c4d7f1917 100644 --- a/roles/lib_openshift/library/oc_project.py +++ b/roles/lib_openshift/library/oc_project.py @@ -33,6 +33,7 @@ from __future__ import print_function import atexit +import copy import json import os import re @@ -40,7 +41,11 @@ import shutil import subprocess import tempfile # pylint: disable=import-error -import ruamel.yaml as yaml +try: + import ruamel.yaml as yaml +except ImportError: + import yaml + from ansible.module_utils.basic import AnsibleModule # -*- -*- -*- End included fragment: lib/import.py -*- -*- -*- @@ -129,6 +134,7 @@ EXAMPLES = ''' # -*- -*- -*- End included fragment: doc/project -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- +# pylint: disable=undefined-variable,missing-docstring # noqa: E301,E302 @@ -323,11 +329,17 @@ class Yedit(object): if self.backup and self.file_exists(): shutil.copy(self.filename, self.filename + '.orig') - # pylint: disable=no-member - if hasattr(self.yaml_dict, 'fa'): + # Try to set format attributes if supported + try: self.yaml_dict.fa.set_block_style() + except AttributeError: + pass - Yedit._write(self.filename, yaml.dump(self.yaml_dict, Dumper=yaml.RoundTripDumper)) + # Try to use RoundTripDumper if supported. + try: + Yedit._write(self.filename, yaml.dump(self.yaml_dict, Dumper=yaml.RoundTripDumper)) + except AttributeError: + Yedit._write(self.filename, yaml.safe_dump(self.yaml_dict, default_flow_style=False)) return (True, self.yaml_dict) @@ -367,10 +379,24 @@ class Yedit(object): # check if it is yaml try: if content_type == 'yaml' and contents: - self.yaml_dict = yaml.load(contents, yaml.RoundTripLoader) - # pylint: disable=no-member - if hasattr(self.yaml_dict, 'fa'): + # Try to set format attributes if supported + try: self.yaml_dict.fa.set_block_style() + except AttributeError: + pass + + # Try to use RoundTripLoader if supported. + try: + self.yaml_dict = yaml.safe_load(contents, yaml.RoundTripLoader) + except AttributeError: + self.yaml_dict = yaml.safe_load(contents) + + # Try to set format attributes if supported + try: + self.yaml_dict.fa.set_block_style() + except AttributeError: + pass + elif content_type == 'json' and contents: self.yaml_dict = json.loads(contents) except yaml.YAMLError as err: @@ -399,14 +425,16 @@ class Yedit(object): return (False, self.yaml_dict) if isinstance(entry, dict): - # pylint: disable=no-member,maybe-no-member + # AUDIT:maybe-no-member makes sense due to fuzzy types + # pylint: disable=maybe-no-member if key_or_item in entry: entry.pop(key_or_item) return (True, self.yaml_dict) return (False, self.yaml_dict) elif isinstance(entry, list): - # pylint: disable=no-member,maybe-no-member + # AUDIT:maybe-no-member makes sense due to fuzzy types + # pylint: disable=maybe-no-member ind = None try: ind = entry.index(key_or_item) @@ -474,7 +502,9 @@ class Yedit(object): if not isinstance(entry, list): return (False, self.yaml_dict) - # pylint: disable=no-member,maybe-no-member + # AUDIT:maybe-no-member makes sense due to loading data from + # a serialized format. + # pylint: disable=maybe-no-member entry.append(value) return (True, self.yaml_dict) @@ -487,7 +517,8 @@ class Yedit(object): entry = None if isinstance(entry, dict): - # pylint: disable=no-member,maybe-no-member + # 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 @@ -496,7 +527,8 @@ class Yedit(object): return (True, self.yaml_dict) elif isinstance(entry, list): - # pylint: disable=no-member,maybe-no-member + # AUDIT:maybe-no-member makes sense due to fuzzy types + # pylint: disable=maybe-no-member ind = None if curr_value: try: @@ -535,12 +567,20 @@ class Yedit(object): return (False, self.yaml_dict) # deepcopy didn't work - tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict, - default_flow_style=False), - yaml.RoundTripLoader) - # pylint: disable=no-member - if hasattr(self.yaml_dict, 'fa'): + # Try to use ruamel.yaml and fallback to pyyaml + try: + tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict, + default_flow_style=False), + yaml.RoundTripLoader) + except AttributeError: + tmp_copy = copy.deepcopy(self.yaml_dict) + + # set the format attributes if available + try: tmp_copy.fa.set_block_style() + except AttributeError: + pass + result = Yedit.add_entry(tmp_copy, path, value, self.separator) if not result: return (False, self.yaml_dict) @@ -553,11 +593,20 @@ class Yedit(object): ''' create a yaml file ''' if not self.file_exists(): # deepcopy didn't work - tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict, default_flow_style=False), # noqa: E501 - yaml.RoundTripLoader) - # pylint: disable=no-member - if hasattr(self.yaml_dict, 'fa'): + # Try to use ruamel.yaml and fallback to pyyaml + try: + tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict, + default_flow_style=False), + yaml.RoundTripLoader) + except AttributeError: + tmp_copy = copy.deepcopy(self.yaml_dict) + + # set the format attributes if available + try: tmp_copy.fa.set_block_style() + except AttributeError: + pass + result = Yedit.add_entry(tmp_copy, path, value, self.separator) if result: self.yaml_dict = tmp_copy @@ -713,6 +762,32 @@ class OpenShiftCLIError(Exception): pass +ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')] + + +def locate_oc_binary(): + ''' Find and return oc binary file ''' + # https://github.com/openshift/openshift-ansible/issues/3410 + # oc can be in /usr/local/bin in some cases, but that may not + # be in $PATH due to ansible/sudo + paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS + + oc_binary = 'oc' + + # Use shutil.which if it is available, otherwise fallback to a naive path search + try: + which_result = shutil.which(oc_binary, path=os.pathsep.join(paths)) + if which_result is not None: + oc_binary = which_result + except AttributeError: + for path in paths: + if os.path.exists(os.path.join(path, oc_binary)): + oc_binary = os.path.join(path, oc_binary) + break + + return oc_binary + + # pylint: disable=too-few-public-methods class OpenShiftCLI(object): ''' Class to wrap the command line tools ''' @@ -726,6 +801,7 @@ class OpenShiftCLI(object): self.verbose = verbose self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig) self.all_namespaces = all_namespaces + self.oc_binary = locate_oc_binary() # Pylint allows only 5 arguments to be passed. # pylint: disable=too-many-arguments @@ -922,24 +998,23 @@ class OpenShiftCLI(object): stdout, stderr = proc.communicate(input_data) - return proc.returncode, stdout, stderr + return proc.returncode, stdout.decode(), stderr.decode() # pylint: disable=too-many-arguments,too-many-branches def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None): '''Base command for oc ''' - cmds = [] + cmds = [self.oc_binary] + if oadm: - cmds = ['oadm'] - else: - cmds = ['oc'] + cmds.append('adm') + + cmds.extend(cmd) if self.all_namespaces: cmds.extend(['--all-namespaces']) elif self.namespace is not None and self.namespace.lower() not in ['none', 'emtpy']: # E501 cmds.extend(['-n', self.namespace]) - cmds.extend(cmd) - rval = {} results = '' err = None @@ -947,7 +1022,10 @@ class OpenShiftCLI(object): if self.verbose: print(' '.join(cmds)) - returncode, stdout, stderr = self._run(cmds, input_data) + try: + returncode, stdout, stderr = self._run(cmds, input_data) + except OSError as ex: + returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex) rval = {"returncode": returncode, "results": results, @@ -999,7 +1077,13 @@ class Utils(object): tmp = Utils.create_tmpfile(prefix=rname) if ftype == 'yaml': - Utils._write(tmp, yaml.dump(data, Dumper=yaml.RoundTripDumper)) + # AUDIT:no-member makes sense here due to ruamel.YAML/PyYAML usage + # pylint: disable=no-member + if hasattr(yaml, 'RoundTripDumper'): + Utils._write(tmp, yaml.dump(data, Dumper=yaml.RoundTripDumper)) + else: + Utils._write(tmp, yaml.safe_dump(data, default_flow_style=False)) + elif ftype == 'json': Utils._write(tmp, json.dumps(data)) else: @@ -1081,7 +1165,12 @@ class Utils(object): contents = sfd.read() if sfile_type == 'yaml': - contents = yaml.load(contents, yaml.RoundTripLoader) + # AUDIT:no-member makes sense here due to ruamel.YAML/PyYAML usage + # pylint: disable=no-member + if hasattr(yaml, 'RoundTripLoader'): + contents = yaml.load(contents, yaml.RoundTripLoader) + else: + contents = yaml.safe_load(contents) elif sfile_type == 'json': contents = json.loads(contents) @@ -1272,24 +1361,25 @@ class OpenShiftCLIConfig(object): class ProjectConfig(OpenShiftCLIConfig): ''' project config object ''' def __init__(self, rname, namespace, kubeconfig, project_options): - super(ProjectConfig, self).__init__(rname, rname, kubeconfig, project_options) + super(ProjectConfig, self).__init__(rname, None, kubeconfig, project_options) + class Project(Yedit): ''' Class to wrap the oc command line tools ''' annotations_path = "metadata.annotations" - kind = 'Service' + kind = 'Project' annotation_prefix = 'openshift.io/' def __init__(self, content): - '''Service constructor''' + '''Project constructor''' super(Project, self).__init__(content=content) def get_annotations(self): - ''' get a list of ports ''' + ''' return the annotations''' return self.get(Project.annotations_path) or {} def add_annotations(self, inc_annos): - ''' add a port object to the ports list ''' + ''' add an annotation to the other annotations''' if not isinstance(inc_annos, list): inc_annos = [inc_annos] @@ -1304,7 +1394,7 @@ class Project(Yedit): return True def find_annotation(self, key): - ''' find a specific port ''' + ''' find an annotation''' annotations = self.get_annotations() for anno in annotations: if Project.annotation_prefix + key == anno: @@ -1332,7 +1422,7 @@ class Project(Yedit): return removed def update_annotation(self, key, value): - ''' remove an annotation from a project''' + ''' remove an annotation for a project''' annos = self.get(Project.annotations_path) or {} if not annos: @@ -1356,7 +1446,7 @@ class Project(Yedit): # pylint: disable=too-many-instance-attributes class OCProject(OpenShiftCLI): - ''' Class to wrap the oc command line tools ''' + ''' Project Class to manage project/namespace objects''' kind = 'namespace' def __init__(self, @@ -1438,7 +1528,6 @@ class OCProject(OpenShiftCLI): if result != self.config.config_options['node_selector']['value']: return True - # Check rolebindings and policybindings return False # pylint: disable=too-many-return-statements,too-many-branches @@ -1483,6 +1572,9 @@ class OCProject(OpenShiftCLI): api_rval = oadm_project.delete() + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + return {'changed': True, 'results': api_rval, 'state': state} return {'changed': False, 'state': state} diff --git a/roles/lib_openshift/src/class/oc_project.py b/roles/lib_openshift/src/class/oc_project.py index cf378ef6d..642d85375 100644 --- a/roles/lib_openshift/src/class/oc_project.py +++ b/roles/lib_openshift/src/class/oc_project.py @@ -4,7 +4,7 @@ # pylint: disable=too-many-instance-attributes class OCProject(OpenShiftCLI): - ''' Class to wrap the oc command line tools ''' + ''' Project Class to manage project/namespace objects''' kind = 'namespace' def __init__(self, @@ -86,7 +86,6 @@ class OCProject(OpenShiftCLI): if result != self.config.config_options['node_selector']['value']: return True - # Check rolebindings and policybindings return False # pylint: disable=too-many-return-statements,too-many-branches @@ -131,6 +130,9 @@ class OCProject(OpenShiftCLI): api_rval = oadm_project.delete() + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + return {'changed': True, 'results': api_rval, 'state': state} return {'changed': False, 'state': state} diff --git a/roles/lib_openshift/src/lib/project.py b/roles/lib_openshift/src/lib/project.py index a06f83d78..40994741c 100644 --- a/roles/lib_openshift/src/lib/project.py +++ b/roles/lib_openshift/src/lib/project.py @@ -6,24 +6,25 @@ class ProjectConfig(OpenShiftCLIConfig): ''' project config object ''' def __init__(self, rname, namespace, kubeconfig, project_options): - super(ProjectConfig, self).__init__(rname, rname, kubeconfig, project_options) + super(ProjectConfig, self).__init__(rname, None, kubeconfig, project_options) + class Project(Yedit): ''' Class to wrap the oc command line tools ''' annotations_path = "metadata.annotations" - kind = 'Service' + kind = 'Project' annotation_prefix = 'openshift.io/' def __init__(self, content): - '''Service constructor''' + '''Project constructor''' super(Project, self).__init__(content=content) def get_annotations(self): - ''' get a list of ports ''' + ''' return the annotations''' return self.get(Project.annotations_path) or {} def add_annotations(self, inc_annos): - ''' add a port object to the ports list ''' + ''' add an annotation to the other annotations''' if not isinstance(inc_annos, list): inc_annos = [inc_annos] @@ -38,7 +39,7 @@ class Project(Yedit): return True def find_annotation(self, key): - ''' find a specific port ''' + ''' find an annotation''' annotations = self.get_annotations() for anno in annotations: if Project.annotation_prefix + key == anno: @@ -66,7 +67,7 @@ class Project(Yedit): return removed def update_annotation(self, key, value): - ''' remove an annotation from a project''' + ''' remove an annotation for a project''' annos = self.get(Project.annotations_path) or {} if not annos: |