From 8cc12c32d35ae0a86f13110d6ea9bdb6a411bbc9 Mon Sep 17 00:00:00 2001 From: Kenny Woodson Date: Thu, 23 Mar 2017 16:24:20 -0400 Subject: Fixing up test cases, linting, and added a return. --- roles/lib_openshift/library/oc_image.py | 268 +++++++++++--------- roles/lib_openshift/src/ansible/oc_image.py | 5 +- roles/lib_openshift/src/class/oc_image.py | 33 +-- roles/lib_openshift/src/doc/image | 71 ++---- roles/lib_openshift/src/test/unit/oc_image.py | 250 ------------------ roles/lib_openshift/src/test/unit/test_oc_image.py | 280 +++++++++++++++++++++ 6 files changed, 475 insertions(+), 432 deletions(-) delete mode 100755 roles/lib_openshift/src/test/unit/oc_image.py create mode 100755 roles/lib_openshift/src/test/unit/test_oc_image.py (limited to 'roles/lib_openshift') diff --git a/roles/lib_openshift/library/oc_image.py b/roles/lib_openshift/library/oc_image.py index 6a07ae49e..f9d07715f 100644 --- a/roles/lib_openshift/library/oc_image.py +++ b/roles/lib_openshift/library/oc_image.py @@ -62,12 +62,10 @@ options: state: description: - State controls the action that will be taken with resource - - 'present' will create or update and object to the desired state - - 'absent' will ensure certain labels are removed + - 'present' will create. Does _not_ support update. - 'list' will read the labels - - 'add' will insert labels to the already existing labels default: present - choices: ["present", "absent", "list", "add"] + choices: ["present", "list"] aliases: [] kubeconfig: description: @@ -81,70 +79,50 @@ options: required: false default: False aliases: [] - kind: + registry_url: description: - - The kind of object that can be managed. - default: node - choices: - - node - - pod - - namespace + - The url for the registry so that openshift can pull the image + required: false + default: None aliases: [] - labels: + image_name: description: - - A list of labels for the resource. - - Each list consists of a key and a value. - - eg, {'key': 'foo', 'value': 'bar'} + - The name of the image being imported required: false - default: None + default: False aliases: [] - selector: + image_tag: description: - - The selector to apply to the resource query + - The tag of the image being imported required: false default: None aliases: [] author: -- "Joel Diaz " +- "Ivan Horvath" extends_documentation_fragment: [] ''' EXAMPLES = ''' -- name: Add a single label to a node's existing labels - oc_label: - name: ip-172-31-5-23.ec2.internal - state: add - kind: node - labels: - - key: logging-infra-fluentd - value: 'true' - -- name: remove a label from a node - oc_label: - name: ip-172-31-5-23.ec2.internal - state: absent - kind: node - labels: - - key: color - value: blue - -- name: Ensure node has these exact labels - oc_label: - name: ip-172-31-5-23.ec2.internal +- name: Get an imagestream + oc_image: + name: php55 + state: list + register: imageout + +- name: create an imagestream + oc_image: state: present - kind: node - labels: - - key: color - value: green - - key: type - value: master - - key: environment - value: production + image_name: php55 + image_tag: int + registry_url: registry.example.com + namespace: default + register: imageout ''' # -*- -*- -*- End included fragment: doc/image -*- -*- -*- # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*- +# pylint: disable=undefined-variable,missing-docstring # noqa: E301,E302 @@ -270,7 +248,8 @@ class Yedit(object): continue elif data and not isinstance(data, dict): - return None + raise YeditException("Unexpected item type found while going through key " + + "path: {} (at key: {})".format(key, dict_key)) data[dict_key] = {} data = data[dict_key] @@ -279,7 +258,7 @@ class Yedit(object): int(arr_ind) <= len(data) - 1): data = data[int(arr_ind)] else: - return None + raise YeditException("Unexpected item type found while going through key path: {}".format(key)) if key == '': data = item @@ -293,6 +272,12 @@ class Yedit(object): elif key_indexes[-1][1] and isinstance(data, dict): data[key_indexes[-1][1]] = item + # didn't add/update to an existing list, nor add/update key to a dict + # so we must have been provided some syntax like a.b.c[] = "data" for a + # non-existent array + else: + raise YeditException("Error adding to object at path: {}".format(key)) + return data @staticmethod @@ -339,14 +324,16 @@ class Yedit(object): if self.backup and self.file_exists(): shutil.copy(self.filename, self.filename + '.orig') - if hasattr(yaml, 'RoundTripDumper'): - # pylint: disable=no-member - if hasattr(self.yaml_dict, 'fa'): - self.yaml_dict.fa.set_block_style() + # Try to set format attributes if supported + try: + self.yaml_dict.fa.set_block_style() + except AttributeError: + pass - # pylint: disable=no-member + # Try to use RoundTripDumper if supported. + try: Yedit._write(self.filename, yaml.dump(self.yaml_dict, Dumper=yaml.RoundTripDumper)) - else: + except AttributeError: Yedit._write(self.filename, yaml.safe_dump(self.yaml_dict, default_flow_style=False)) return (True, self.yaml_dict) @@ -387,15 +374,23 @@ class Yedit(object): # check if it is yaml try: if content_type == 'yaml' and contents: - # pylint: disable=no-member - if hasattr(yaml, 'RoundTripLoader'): - self.yaml_dict = yaml.load(contents, yaml.RoundTripLoader) - else: + # 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) - # 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 elif content_type == 'json' and contents: self.yaml_dict = json.loads(contents) @@ -425,14 +420,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) @@ -500,7 +497,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) @@ -513,7 +512,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 @@ -522,7 +522,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: @@ -561,19 +562,20 @@ class Yedit(object): return (False, self.yaml_dict) # deepcopy didn't work - if hasattr(yaml, 'round_trip_dump'): - # pylint: disable=no-member + # 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) - - # pylint: disable=no-member - if hasattr(self.yaml_dict, 'fa'): - tmp_copy.fa.set_block_style() - - else: + 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) @@ -586,17 +588,20 @@ class Yedit(object): ''' create a yaml file ''' if not self.file_exists(): # deepcopy didn't work - if hasattr(yaml, 'round_trip_dump'): - # pylint: disable=no-member - tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict, default_flow_style=False), # noqa: E501 + # 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) - - # pylint: disable=no-member - if hasattr(self.yaml_dict, 'fa'): - tmp_copy.fa.set_block_style() - else: + 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 @@ -752,6 +757,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 ''' @@ -765,6 +796,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 @@ -966,19 +998,18 @@ class OpenShiftCLI(object): # 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 @@ -986,7 +1017,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, @@ -997,9 +1031,9 @@ class OpenShiftCLI(object): if output_type == 'json': try: rval['results'] = json.loads(stdout) - except ValueError as err: - if "No JSON object could be decoded" in err.args: - err = err.args + except ValueError as verr: + if "No JSON object could be decoded" in verr.args: + err = verr.args elif output_type == 'raw': rval['results'] = stdout @@ -1038,6 +1072,7 @@ class Utils(object): tmp = Utils.create_tmpfile(prefix=rname) if ftype == 'yaml': + # 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)) @@ -1125,6 +1160,7 @@ class Utils(object): contents = sfd.read() if sfile_type == 'yaml': + # 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) @@ -1235,8 +1271,8 @@ class Utils(object): elif value != user_def[key]: if debug: print('value should be identical') - print(value) print(user_def[key]) + print(value) return False # recurse on a dictionary @@ -1256,8 +1292,8 @@ class Utils(object): if api_values != user_values: if debug: print("keys are not equal in dict") - print(api_values) print(user_values) + print(api_values) return False result = Utils.check_def_equal(user_def[key], value, skip_keys=skip_keys, debug=debug) @@ -1303,10 +1339,11 @@ class OpenShiftCLIConfig(object): def stringify(self): ''' return the options hash as cli params in a string ''' rval = [] - for key, data in self.config_options.items(): + for key in sorted(self.config_options.keys()): + data = self.config_options[key] if data['include'] \ and (data['value'] or isinstance(data['value'], int)): - rval.append('--%s=%s' % (key.replace('_', '-'), data['value'])) + rval.append('--{}={}'.format(key.replace('_', '-'), data['value'])) return rval @@ -1315,10 +1352,10 @@ class OpenShiftCLIConfig(object): # -*- -*- -*- Begin included fragment: class/oc_image.py -*- -*- -*- + # pylint: disable=too-many-arguments class OCImage(OpenShiftCLI): - ''' Class to wrap the oc command line tools - ''' + ''' Class to import and create an imagestream object''' def __init__(self, namespace, registry_url, @@ -1326,13 +1363,11 @@ class OCImage(OpenShiftCLI): image_tag, kubeconfig='/etc/origin/master/admin.kubeconfig', verbose=False): - ''' Constructor for OpenshiftOC ''' + ''' Constructor for OCImage''' super(OCImage, self).__init__(namespace, kubeconfig) - self.namespace = namespace self.registry_url = registry_url self.image_name = image_name self.image_tag = image_tag - self.kubeconfig = kubeconfig self.verbose = verbose def get(self): @@ -1342,21 +1377,21 @@ class OCImage(OpenShiftCLI): if results['returncode'] == 0 and results['results'][0]: results['exists'] = True - if results['returncode'] != 0 and '"%s" not found' % self.image_name in results['stderr']: + if results['returncode'] != 0 and '"{}" not found'.format(self.image_name) in results['stderr']: results['returncode'] = 0 return results def create(self, url=None, name=None, tag=None): '''Create an image ''' - return self._import_image(url, name, tag) + # pylint: disable=too-many-return-statements @staticmethod def run_ansible(params, check_mode): ''' run the ansible idempotent code ''' - + ocimage = OCImage(params['namespace'], params['registry_url'], params['image_name'], @@ -1376,14 +1411,11 @@ class OCImage(OpenShiftCLI): return {"failed": True, "msg": api_rval} return {"changed": False, "results": api_rval, "state": "list"} - if not params['image_name']: - return {"failed": True, "msg": 'Please specify a name when state is absent|present.'} - + ######## + # Create + ######## if state == 'present': - ######## - # Create - ######## if not Utils.exists(api_rval['results'], params['image_name']): if check_mode: @@ -1396,13 +1428,18 @@ class OCImage(OpenShiftCLI): if api_rval['returncode'] != 0: return {"failed": True, "msg": api_rval} - return {"changed": True, "results": api_rval, "state": "present"} + # return the newly created object + api_rval = ocimage.get() + + if api_rval['returncode'] != 0: + return {"failed": True, "msg": api_rval} + return {"changed": True, "results": api_rval, "state": "present"} # image exists, no change return {"changed": False, "results": api_rval, "state": "present"} - return {"failed": True, "changed": False, "results": "Unknown state passed. {0}".format(state), "state": "unknown"} + return {"failed": True, "changed": False, "msg": "Unknown state passed. {0}".format(state)} # -*- -*- -*- End included fragment: class/oc_image.py -*- -*- -*- @@ -1422,9 +1459,8 @@ def main(): debug=dict(default=False, type='bool'), namespace=dict(default='default', type='str'), registry_url=dict(default=None, type='str'), - image_name=dict(default=None, type='str'), + image_name=dict(default=None, required=True, type='str'), image_tag=dict(default=None, type='str'), - content_type=dict(default='raw', choices=['yaml', 'json', 'raw'], type='str'), force=dict(default=False, type='bool'), ), @@ -1432,12 +1468,12 @@ def main(): ) rval = OCImage.run_ansible(module.params, module.check_mode) + if 'failed' in rval: module.fail_json(**rval) module.exit_json(**rval) - if __name__ == '__main__': main() diff --git a/roles/lib_openshift/src/ansible/oc_image.py b/roles/lib_openshift/src/ansible/oc_image.py index f7c52ace0..447d62f20 100644 --- a/roles/lib_openshift/src/ansible/oc_image.py +++ b/roles/lib_openshift/src/ansible/oc_image.py @@ -15,9 +15,8 @@ def main(): debug=dict(default=False, type='bool'), namespace=dict(default='default', type='str'), registry_url=dict(default=None, type='str'), - image_name=dict(default=None, type='str'), + image_name=dict(default=None, required=True, type='str'), image_tag=dict(default=None, type='str'), - content_type=dict(default='raw', choices=['yaml', 'json', 'raw'], type='str'), force=dict(default=False, type='bool'), ), @@ -25,11 +24,11 @@ def main(): ) rval = OCImage.run_ansible(module.params, module.check_mode) + if 'failed' in rval: module.fail_json(**rval) module.exit_json(**rval) - if __name__ == '__main__': main() diff --git a/roles/lib_openshift/src/class/oc_image.py b/roles/lib_openshift/src/class/oc_image.py index 619e58057..d25349127 100644 --- a/roles/lib_openshift/src/class/oc_image.py +++ b/roles/lib_openshift/src/class/oc_image.py @@ -1,9 +1,10 @@ # pylint: skip-file +# flake8: noqa + # pylint: disable=too-many-arguments class OCImage(OpenShiftCLI): - ''' Class to wrap the oc command line tools - ''' + ''' Class to import and create an imagestream object''' def __init__(self, namespace, registry_url, @@ -11,13 +12,11 @@ class OCImage(OpenShiftCLI): image_tag, kubeconfig='/etc/origin/master/admin.kubeconfig', verbose=False): - ''' Constructor for OpenshiftOC ''' + ''' Constructor for OCImage''' super(OCImage, self).__init__(namespace, kubeconfig) - self.namespace = namespace self.registry_url = registry_url self.image_name = image_name self.image_tag = image_tag - self.kubeconfig = kubeconfig self.verbose = verbose def get(self): @@ -27,21 +26,21 @@ class OCImage(OpenShiftCLI): if results['returncode'] == 0 and results['results'][0]: results['exists'] = True - if results['returncode'] != 0 and '"%s" not found' % self.image_name in results['stderr']: + if results['returncode'] != 0 and '"{}" not found'.format(self.image_name) in results['stderr']: results['returncode'] = 0 return results def create(self, url=None, name=None, tag=None): '''Create an image ''' - return self._import_image(url, name, tag) + # pylint: disable=too-many-return-statements @staticmethod def run_ansible(params, check_mode): ''' run the ansible idempotent code ''' - + ocimage = OCImage(params['namespace'], params['registry_url'], params['image_name'], @@ -61,14 +60,11 @@ class OCImage(OpenShiftCLI): return {"failed": True, "msg": api_rval} return {"changed": False, "results": api_rval, "state": "list"} - if not params['image_name']: - return {"failed": True, "msg": 'Please specify a name when state is absent|present.'} - + ######## + # Create + ######## if state == 'present': - ######## - # Create - ######## if not Utils.exists(api_rval['results'], params['image_name']): if check_mode: @@ -81,10 +77,15 @@ class OCImage(OpenShiftCLI): if api_rval['returncode'] != 0: return {"failed": True, "msg": api_rval} - return {"changed": True, "results": api_rval, "state": "present"} + # return the newly created object + api_rval = ocimage.get() + if api_rval['returncode'] != 0: + return {"failed": True, "msg": api_rval} + + return {"changed": True, "results": api_rval, "state": "present"} # image exists, no change return {"changed": False, "results": api_rval, "state": "present"} - return {"failed": True, "changed": False, "results": "Unknown state passed. {0}".format(state), "state": "unknown"} + return {"failed": True, "changed": False, "msg": "Unknown state passed. {0}".format(state)} diff --git a/roles/lib_openshift/src/doc/image b/roles/lib_openshift/src/doc/image index fb3ed2503..8a5507ca4 100644 --- a/roles/lib_openshift/src/doc/image +++ b/roles/lib_openshift/src/doc/image @@ -11,12 +11,10 @@ options: state: description: - State controls the action that will be taken with resource - - 'present' will create or update and object to the desired state - - 'absent' will ensure certain labels are removed + - 'present' will create. Does _not_ support update. - 'list' will read the labels - - 'add' will insert labels to the already existing labels default: present - choices: ["present", "absent", "list", "add"] + choices: ["present", "list"] aliases: [] kubeconfig: description: @@ -30,63 +28,42 @@ options: required: false default: False aliases: [] - kind: + registry_url: description: - - The kind of object that can be managed. - default: node - choices: - - node - - pod - - namespace + - The url for the registry so that openshift can pull the image + required: false + default: None aliases: [] - labels: + image_name: description: - - A list of labels for the resource. - - Each list consists of a key and a value. - - eg, {'key': 'foo', 'value': 'bar'} + - The name of the image being imported required: false - default: None + default: False aliases: [] - selector: + image_tag: description: - - The selector to apply to the resource query + - The tag of the image being imported required: false default: None aliases: [] author: -- "Joel Diaz " +- "Ivan Horvath" extends_documentation_fragment: [] ''' EXAMPLES = ''' -- name: Add a single label to a node's existing labels - oc_label: - name: ip-172-31-5-23.ec2.internal - state: add - kind: node - labels: - - key: logging-infra-fluentd - value: 'true' - -- name: remove a label from a node - oc_label: - name: ip-172-31-5-23.ec2.internal - state: absent - kind: node - labels: - - key: color - value: blue +- name: Get an imagestream + oc_image: + name: php55 + state: list + register: imageout -- name: Ensure node has these exact labels - oc_label: - name: ip-172-31-5-23.ec2.internal +- name: create an imagestream + oc_image: state: present - kind: node - labels: - - key: color - value: green - - key: type - value: master - - key: environment - value: production + image_name: php55 + image_tag: int + registry_url: registry.example.com + namespace: default + register: imageout ''' diff --git a/roles/lib_openshift/src/test/unit/oc_image.py b/roles/lib_openshift/src/test/unit/oc_image.py deleted file mode 100755 index 13e850ee1..000000000 --- a/roles/lib_openshift/src/test/unit/oc_image.py +++ /dev/null @@ -1,250 +0,0 @@ -#!/usr/bin/env python2 -''' - Unit tests for oc label -''' -# To run -# python -m unittest image -# -# . -# Ran 1 test in 0.597s -# -# OK - -import os -import sys -import unittest -import mock - -# Removing invalid variable names for tests so that I can -# keep them brief -# pylint: disable=invalid-name,no-name-in-module -# Disable import-error b/c our libraries aren't loaded in jenkins -# pylint: disable=import-error -# place class in our python path -module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library') # noqa: E501 -sys.path.insert(0, module_path) -from oc_image import OCImage # noqa: E402 - - -class OCImageTest(unittest.TestCase): - ''' - Test class for OCImage - ''' - - def setUp(self): - ''' setup method will create a file and set to known configuration ''' - pass - - @mock.patch('oc_image.Utils.create_tmpfile_copy') - @mock.patch('oc_image.OCImage._run') - def test_state_list(self, mock_cmd, mock_tmpfile_copy): - ''' Testing a image list ''' - params = {'registry_url': 'registry.ops.openshift.com', - 'image_name': 'oso-rhel7-zagg-web', - 'image_tag': 'int', - 'name': 'default', - 'namespace': 'default', - 'labels': None, - 'state': 'list', - 'kind': 'namespace', - 'selector': None, - 'kubeconfig': '/etc/origin/master/admin.kubeconfig', - 'debug': False} - - - istream = '''{ - "kind": "ImageStream", - "apiVersion": "v1", - "metadata": { - "name": "oso-rhel7-zagg-web", - "namespace": "default", - "selfLink": "/oapi/v1/namespaces/default/imagestreams/oso-rhel7-zagg-web", - "uid": "6ca2b199-dcdb-11e6-8ffd-0a5f8e3e32be", - "resourceVersion": "8135944", - "generation": 1, - "creationTimestamp": "2017-01-17T17:36:05Z", - "annotations": { - "openshift.io/image.dockerRepositoryCheck": "2017-01-17T17:36:05Z" - } - }, - "spec": { - "tags": [ - { - "name": "int", - "annotations": null, - "from": { - "kind": "DockerImage", - "name": "registry.ops.openshift.com/ops/oso-rhel7-zagg-web:int" - }, - "generation": 1, - "importPolicy": {} - } - ] - }, - "status": { - "dockerImageRepository": "172.30.183.164:5000/default/oso-rhel7-zagg-web", - "tags": [ - { - "tag": "int", - "items": [ - { - "created": "2017-01-17T17:36:05Z", - "dockerImageReference": "registry.ops.openshift.com/ops/oso-rhel7-zagg-web@sha256:645bab780cf18a9b764d64b02ca65c39d13cb16f19badd0a49a1668629759392", - "image": "sha256:645bab780cf18a9b764d64b02ca65c39d13cb16f19badd0a49a1668629759392", - "generation": 1 - } - ] - } - ] - } - } - ''' - - mock_cmd.side_effect = [ - (0, istream, ''), - ] - - mock_tmpfile_copy.side_effect = [ - '/tmp/mocked_kubeconfig', - ] - - results = OCImage.run_ansible(params, False) - - self.assertFalse(results['changed']) - self.assertEquals(results['results']['results'][0]['metadata']['name'], 'oso-rhel7-zagg-web') - - @mock.patch('oc_image.Utils.create_tmpfile_copy') - @mock.patch('oc_image.OCImage._run') - def test_state_present(self, mock_cmd, mock_tmpfile_copy): - ''' Testing a image list ''' - params = {'registry_url': 'registry.ops.openshift.com', - 'image_name': 'oso-rhel7-zagg-web', - 'image_tag': 'int', - 'name': 'default', - 'namespace': 'default', - 'state': 'present', - 'kind': 'namespace', - 'selector': None, - 'kubeconfig': '/etc/origin/master/admin.kubeconfig', - 'debug': False} - - - istream = '''{ - "kind": "ImageStream", - "apiVersion": "v1", - "metadata": { - "name": "oso-rhel7-zagg-web", - "namespace": "default", - "selfLink": "/oapi/v1/namespaces/default/imagestreams/oso-rhel7-zagg-web", - "uid": "6ca2b199-dcdb-11e6-8ffd-0a5f8e3e32be", - "resourceVersion": "8135944", - "generation": 1, - "creationTimestamp": "2017-01-17T17:36:05Z", - "annotations": { - "openshift.io/image.dockerRepositoryCheck": "2017-01-17T17:36:05Z" - } - }, - "spec": { - "tags": [ - { - "name": "int", - "annotations": null, - "from": { - "kind": "DockerImage", - "name": "registry.ops.openshift.com/ops/oso-rhel7-zagg-web:int" - }, - "generation": 1, - "importPolicy": {} - } - ] - }, - "status": { - "dockerImageRepository": "172.30.183.164:5000/default/oso-rhel7-zagg-web", - "tags": [ - { - "tag": "int", - "items": [ - { - "created": "2017-01-17T17:36:05Z", - "dockerImageReference": "registry.ops.openshift.com/ops/oso-rhel7-zagg-web@sha256:645bab780cf18a9b764d64b02ca65c39d13cb16f19badd0a49a1668629759392", - "image": "sha256:645bab780cf18a9b764d64b02ca65c39d13cb16f19badd0a49a1668629759392", - "generation": 1 - } - ] - } - ] - } - } - ''' - istream1 = '''{ - "kind": "ImageStream", - "apiVersion": "v1", - "metadata": { - "name": "oso-rhel7-zagg-web", - "namespace": "default", - "selfLink": "/oapi/v1/namespaces/default/imagestreams/oso-rhel7-zagg-web", - "uid": "6ca2b199-dcdb-11e6-8ffd-0a5f8e3e32be", - "resourceVersion": "8135944", - "generation": 1, - "creationTimestamp": "2017-01-17T17:36:05Z", - "annotations": { - "openshift.io/image.dockerRepositoryCheck": "2017-01-17T17:36:05Z" - } - }, - "spec": { - "tags": [ - { - "name": "int", - "annotations": null, - "from": { - "kind": "DockerImage", - "name": "registry.ops.openshift.com/ops/oso-rhel7-zagg-web:int" - }, - "generation": 1, - "importPolicy": {} - } - ] - }, - "status": { - "dockerImageRepository": "172.30.183.164:5000/default/oso-rhel7-zagg-web", - "tags": [ - { - "tag": "int", - "items": [ - { - "created": "2017-01-17T17:36:05Z", - "dockerImageReference": "registry.ops.openshift.com/ops/oso-rhel7-zagg-web@sha256:645bab780cf18a9b764d64b02ca65c39d13cb16f19badd0a49a1668629759392", - "image": "sha256:645bab780cf18a9b764d64b02ca65c39d13cb16f19badd0a49a1668629759392", - "generation": 1 - } - ] - } - ] - } - } - ''' - - - mock_cmd.side_effect = [ - (0, istream, ''), - (0, '', ''), - (0, istream1, ''), - ] - - mock_tmpfile_copy.side_effect = [ - '/tmp/mocked_kubeconfig', - ] - - results = OCImage.run_ansible(params, False) - - self.assertTrue(results['changed']) - self.assertTrue(results['results']['results']['labels'][0] == - {'storage_pv_quota': 'False', 'awesomens': 'testinglabel'}) - - def tearDown(self): - '''TearDown method''' - pass - - -if __name__ == "__main__": - unittest.main() diff --git a/roles/lib_openshift/src/test/unit/test_oc_image.py b/roles/lib_openshift/src/test/unit/test_oc_image.py new file mode 100755 index 000000000..943c8ca17 --- /dev/null +++ b/roles/lib_openshift/src/test/unit/test_oc_image.py @@ -0,0 +1,280 @@ +''' + Unit tests for oc image +''' +import os +import sys +import unittest +import mock +import six + +# Removing invalid variable names for tests so that I can +# keep them brief +# pylint: disable=invalid-name,no-name-in-module +# Disable import-error b/c our libraries aren't loaded in jenkins +# pylint: disable=import-error +# place class in our python path +module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library') # noqa: E501 +sys.path.insert(0, module_path) +from oc_image import OCImage, locate_oc_binary # noqa: E402 + + +class OCImageTest(unittest.TestCase): + ''' + Test class for OCImage + ''' + + @mock.patch('oc_image.Utils.create_tmpfile_copy') + @mock.patch('oc_image.OCImage._run') + def test_state_list(self, mock_cmd, mock_tmpfile_copy): + ''' Testing a label list ''' + params = {'registry_url': 'registry.ops.openshift.com', + 'image_name': 'oso-rhel7-zagg-web', + 'image_tag': 'int', + 'namespace': 'default', + 'state': 'list', + 'kubeconfig': '/etc/origin/master/admin.kubeconfig', + 'debug': False} + + istream = '''{ + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "oso-rhel7-zagg-web", + "namespace": "default", + "selfLink": "/oapi/v1/namespaces/default/imagestreams/oso-rhel7-zagg-web", + "uid": "6ca2b199-dcdb-11e6-8ffd-0a5f8e3e32be", + "resourceVersion": "8135944", + "generation": 1, + "creationTimestamp": "2017-01-17T17:36:05Z", + "annotations": { + "openshift.io/image.dockerRepositoryCheck": "2017-01-17T17:36:05Z" + } + }, + "spec": { + "tags": [ + { + "name": "int", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "registry.ops.openshift.com/ops/oso-rhel7-zagg-web:int" + }, + "generation": 1, + "importPolicy": {} + } + ] + }, + "status": { + "dockerImageRepository": "172.30.183.164:5000/default/oso-rhel7-zagg-web", + "tags": [ + { + "tag": "int", + "items": [ + { + "created": "2017-01-17T17:36:05Z", + "dockerImageReference": "registry.ops.openshift.com/ops/oso-rhel7-zagg-web@sha256:645bab780cf18a9b764d64b02ca65c39d13cb16f19badd0a49a1668629759392", + "image": "sha256:645bab780cf18a9b764d64b02ca65c39d13cb16f19badd0a49a1668629759392", + "generation": 1 + } + ] + } + ] + } + } + ''' + + mock_cmd.side_effect = [ + (0, istream, ''), + ] + + mock_tmpfile_copy.side_effect = [ + '/tmp/mocked_kubeconfig', + ] + + results = OCImage.run_ansible(params, False) + + self.assertFalse(results['changed']) + self.assertEquals(results['results']['results'][0]['metadata']['name'], 'oso-rhel7-zagg-web') + + @mock.patch('oc_image.Utils.create_tmpfile_copy') + @mock.patch('oc_image.OCImage._run') + def test_state_present(self, mock_cmd, mock_tmpfile_copy): + ''' Testing a image present ''' + params = {'registry_url': 'registry.ops.openshift.com', + 'image_name': 'oso-rhel7-zagg-web', + 'image_tag': 'int', + 'namespace': 'default', + 'state': 'present', + 'kubeconfig': '/etc/origin/master/admin.kubeconfig', + 'debug': False} + + istream = '''{ + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "oso-rhel7-zagg-web", + "namespace": "default", + "selfLink": "/oapi/v1/namespaces/default/imagestreams/oso-rhel7-zagg-web", + "uid": "6ca2b199-dcdb-11e6-8ffd-0a5f8e3e32be", + "resourceVersion": "8135944", + "generation": 1, + "creationTimestamp": "2017-01-17T17:36:05Z", + "annotations": { + "openshift.io/image.dockerRepositoryCheck": "2017-01-17T17:36:05Z" + } + }, + "spec": { + "tags": [ + { + "name": "int", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "registry.ops.openshift.com/ops/oso-rhel7-zagg-web:int" + }, + "generation": 1, + "importPolicy": {} + } + ] + }, + "status": { + "dockerImageRepository": "172.30.183.164:5000/default/oso-rhel7-zagg-web", + "tags": [ + { + "tag": "int", + "items": [ + { + "created": "2017-01-17T17:36:05Z", + "dockerImageReference": "registry.ops.openshift.com/ops/oso-rhel7-zagg-web@sha256:645bab780cf18a9b764d64b02ca65c39d13cb16f19badd0a49a1668629759392", + "image": "sha256:645bab780cf18a9b764d64b02ca65c39d13cb16f19badd0a49a1668629759392", + "generation": 1 + } + ] + } + ] + } + } + ''' + + mock_cmd.side_effect = [ + (1, '', 'Error from server: imagestreams "oso-rhel7-zagg-web" not found'), + (0, '', ''), + (0, istream, ''), + ] + + mock_tmpfile_copy.side_effect = [ + '/tmp/mocked_kubeconfig', + ] + + results = OCImage.run_ansible(params, False) + + self.assertTrue(results['changed']) + self.assertTrue(results['results']['results'][0]['metadata']['name'] == 'oso-rhel7-zagg-web') + + @unittest.skipIf(six.PY3, 'py2 test only') + @mock.patch('os.path.exists') + @mock.patch('os.environ.get') + def test_binary_lookup_fallback(self, mock_env_get, mock_path_exists): + ''' Testing binary lookup fallback ''' + + mock_env_get.side_effect = lambda _v, _d: '' + + mock_path_exists.side_effect = lambda _: False + + self.assertEqual(locate_oc_binary(), 'oc') + + @unittest.skipIf(six.PY3, 'py2 test only') + @mock.patch('os.path.exists') + @mock.patch('os.environ.get') + def test_binary_lookup_in_path(self, mock_env_get, mock_path_exists): + ''' Testing binary lookup in path ''' + + oc_bin = '/usr/bin/oc' + + mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin' + + mock_path_exists.side_effect = lambda f: f == oc_bin + + self.assertEqual(locate_oc_binary(), oc_bin) + + @unittest.skipIf(six.PY3, 'py2 test only') + @mock.patch('os.path.exists') + @mock.patch('os.environ.get') + def test_binary_lookup_in_usr_local(self, mock_env_get, mock_path_exists): + ''' Testing binary lookup in /usr/local/bin ''' + + oc_bin = '/usr/local/bin/oc' + + mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin' + + mock_path_exists.side_effect = lambda f: f == oc_bin + + self.assertEqual(locate_oc_binary(), oc_bin) + + @unittest.skipIf(six.PY3, 'py2 test only') + @mock.patch('os.path.exists') + @mock.patch('os.environ.get') + def test_binary_lookup_in_home(self, mock_env_get, mock_path_exists): + ''' Testing binary lookup in ~/bin ''' + + oc_bin = os.path.expanduser('~/bin/oc') + + mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin' + + mock_path_exists.side_effect = lambda f: f == oc_bin + + self.assertEqual(locate_oc_binary(), oc_bin) + + @unittest.skipIf(six.PY2, 'py3 test only') + @mock.patch('shutil.which') + @mock.patch('os.environ.get') + def test_binary_lookup_fallback_py3(self, mock_env_get, mock_shutil_which): + ''' Testing binary lookup fallback ''' + + mock_env_get.side_effect = lambda _v, _d: '' + + mock_shutil_which.side_effect = lambda _f, path=None: None + + self.assertEqual(locate_oc_binary(), 'oc') + + @unittest.skipIf(six.PY2, 'py3 test only') + @mock.patch('shutil.which') + @mock.patch('os.environ.get') + def test_binary_lookup_in_path_py3(self, mock_env_get, mock_shutil_which): + ''' Testing binary lookup in path ''' + + oc_bin = '/usr/bin/oc' + + mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin' + + mock_shutil_which.side_effect = lambda _f, path=None: oc_bin + + self.assertEqual(locate_oc_binary(), oc_bin) + + @unittest.skipIf(six.PY2, 'py3 test only') + @mock.patch('shutil.which') + @mock.patch('os.environ.get') + def test_binary_lookup_in_usr_local_py3(self, mock_env_get, mock_shutil_which): + ''' Testing binary lookup in /usr/local/bin ''' + + oc_bin = '/usr/local/bin/oc' + + mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin' + + mock_shutil_which.side_effect = lambda _f, path=None: oc_bin + + self.assertEqual(locate_oc_binary(), oc_bin) + + @unittest.skipIf(six.PY2, 'py3 test only') + @mock.patch('shutil.which') + @mock.patch('os.environ.get') + def test_binary_lookup_in_home_py3(self, mock_env_get, mock_shutil_which): + ''' Testing binary lookup in ~/bin ''' + + oc_bin = os.path.expanduser('~/bin/oc') + + mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin' + + mock_shutil_which.side_effect = lambda _f, path=None: oc_bin + + self.assertEqual(locate_oc_binary(), oc_bin) -- cgit v1.2.3