From 611e9864e8716ca92a77745403c847c30f2f0d87 Mon Sep 17 00:00:00 2001 From: Kenny Woodson Date: Tue, 28 Mar 2017 13:56:00 -0400 Subject: Adding clusterrole to the toolbox. --- roles/lib_openshift/src/ansible/oc_clusterrole.py | 29 ++++ roles/lib_openshift/src/class/oc_clusterrole.py | 163 +++++++++++++++++++++ roles/lib_openshift/src/doc/clusterrole | 66 +++++++++ roles/lib_openshift/src/lib/clusterrole.py | 68 +++++++++ roles/lib_openshift/src/lib/rule.py | 143 ++++++++++++++++++ roles/lib_openshift/src/sources.yml | 12 ++ .../src/test/integration/oc_clusterrole.yml | 106 ++++++++++++++ .../src/test/unit/test_oc_clusterrole.py | 116 +++++++++++++++ 8 files changed, 703 insertions(+) create mode 100644 roles/lib_openshift/src/ansible/oc_clusterrole.py create mode 100644 roles/lib_openshift/src/class/oc_clusterrole.py create mode 100644 roles/lib_openshift/src/doc/clusterrole create mode 100644 roles/lib_openshift/src/lib/clusterrole.py create mode 100644 roles/lib_openshift/src/lib/rule.py create mode 100755 roles/lib_openshift/src/test/integration/oc_clusterrole.yml create mode 100755 roles/lib_openshift/src/test/unit/test_oc_clusterrole.py (limited to 'roles/lib_openshift/src') diff --git a/roles/lib_openshift/src/ansible/oc_clusterrole.py b/roles/lib_openshift/src/ansible/oc_clusterrole.py new file mode 100644 index 000000000..7e4319d2c --- /dev/null +++ b/roles/lib_openshift/src/ansible/oc_clusterrole.py @@ -0,0 +1,29 @@ +# pylint: skip-file +# flake8: noqa + +def main(): + ''' + ansible oc module for clusterrole + ''' + + module = AnsibleModule( + argument_spec=dict( + kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'), + state=dict(default='present', type='str', + choices=['present', 'absent', 'list']), + debug=dict(default=False, type='bool'), + name=dict(default=None, type='str'), + rules=dict(default=None, type='list'), + ), + supports_check_mode=True, + ) + + results = OCClusterRole.run_ansible(module.params, module.check_mode) + + if 'failed' in results: + module.fail_json(**results) + + module.exit_json(**results) + +if __name__ == '__main__': + main() diff --git a/roles/lib_openshift/src/class/oc_clusterrole.py b/roles/lib_openshift/src/class/oc_clusterrole.py new file mode 100644 index 000000000..0d8ed42ba --- /dev/null +++ b/roles/lib_openshift/src/class/oc_clusterrole.py @@ -0,0 +1,163 @@ +# pylint: skip-file +# flake8: noqa + + +# pylint: disable=too-many-instance-attributes +class OCClusterRole(OpenShiftCLI): + ''' Class to manage clusterrole objects''' + kind = 'clusterrole' + + def __init__(self, + name, + rules=None, + kubeconfig=None, + verbose=False): + ''' Constructor for OCClusterRole ''' + super(OCClusterRole, self).__init__(None, kubeconfig=kubeconfig, verbose=verbose) + self.verbose = verbose + self.name = name + self._clusterrole = None + self._inc_clusterrole = ClusterRole.builder(name, rules) + + @property + def clusterrole(self): + ''' property for clusterrole''' + if not self._clusterrole: + self.get() + return self._clusterrole + + @clusterrole.setter + def clusterrole(self, data): + ''' setter function for clusterrole property''' + self._clusterrole = data + + @property + def inc_clusterrole(self): + ''' property for inc_clusterrole''' + return self._inc_clusterrole + + @inc_clusterrole.setter + def inc_clusterrole(self, data): + ''' setter function for inc_clusterrole property''' + self._inc_clusterrole = data + + def exists(self): + ''' return whether a clusterrole exists ''' + if self.clusterrole: + return True + + return False + + def get(self): + '''return a clusterrole ''' + result = self._get(self.kind, self.name) + + if result['returncode'] == 0: + self.clusterrole = ClusterRole(content=result['results'][0]) + result['results'] = self.clusterrole.yaml_dict + + elif 'clusterrole "{}" not found'.format(self.name) in result['stderr']: + result['returncode'] = 0 + + return result + + def delete(self): + '''delete the object''' + return self._delete(self.kind, self.name) + + def create(self): + '''create a clusterrole from the proposed incoming clusterrole''' + return self._create_from_content(self.name, self.inc_clusterrole.yaml_dict) + + def update(self): + '''update a project''' + return self._replace_content(self.kind, self.name, self.inc_clusterrole.yaml_dict) + + def needs_update(self): + ''' verify an update is needed''' + return not self.clusterrole.compare(self.inc_clusterrole, self.verbose) + + # pylint: disable=too-many-return-statements,too-many-branches + @staticmethod + def run_ansible(params, check_mode): + '''run the idempotent ansible code''' + + oc_clusterrole = OCClusterRole(params['name'], + params['rules'], + params['kubeconfig'], + params['debug']) + + state = params['state'] + + api_rval = oc_clusterrole.get() + + ##### + # Get + ##### + if state == 'list': + return {'changed': False, 'results': api_rval, 'state': state} + + ######## + # Delete + ######## + if state == 'absent': + if oc_clusterrole.exists(): + + if check_mode: + return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a delete.'} + + api_rval = oc_clusterrole.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} + + if state == 'present': + ######## + # Create + ######## + if not oc_clusterrole.exists(): + + if check_mode: + return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'} + + # Create it here + api_rval = oc_clusterrole.create() + + # return the created object + api_rval = oc_clusterrole.get() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + return {'changed': True, 'results': api_rval, 'state': state} + + ######## + # Update + ######## + if oc_clusterrole.needs_update(): + + if check_mode: + return {'changed': True, 'msg': 'CHECK_MODE: Would have performed an update.'} + + api_rval = oc_clusterrole.update() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + # return the created object + api_rval = oc_clusterrole.get() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + return {'changed': True, 'results': api_rval, 'state': state} + + return {'changed': False, 'results': api_rval, 'state': state} + + return {'failed': True, + 'changed': False, + 'msg': 'Unknown state passed. [%s]' % state} diff --git a/roles/lib_openshift/src/doc/clusterrole b/roles/lib_openshift/src/doc/clusterrole new file mode 100644 index 000000000..3d14a2dfb --- /dev/null +++ b/roles/lib_openshift/src/doc/clusterrole @@ -0,0 +1,66 @@ +# flake8: noqa +# pylint: skip-file + +DOCUMENTATION = ''' +--- +module: oc_clusterrole +short_description: Modify, and idempotently manage openshift clusterroles +description: + - Manage openshift clusterroles +options: + state: + description: + - Supported states, present, absent, list + - present - will ensure object is created or updated to the value specified + - list - will return a clusterrole + - absent - will remove a clusterrole + required: False + default: present + choices: ["present", 'absent', 'list'] + aliases: [] + kubeconfig: + description: + - The path for the kubeconfig file to use for authentication + required: false + default: /etc/origin/master/admin.kubeconfig + aliases: [] + debug: + description: + - Turn on debug output. + required: false + default: False + aliases: [] + name: + description: + - Name of the object that is being queried. + required: false + default: None + aliases: [] + rules: + description: + - A list of dictionaries that have the rule parameters. + - e.g. rules=[{'apiGroups': [""], 'attributeRestrictions': None, 'verbs': ['get'], 'resources': []}] + required: false + default: None + aliases: [] +author: +- "Kenny Woodson " +extends_documentation_fragment: [] +''' + +EXAMPLES = ''' +- name: query a list of env vars on dc + oc_clusterrole: + name: myclusterrole + state: list + +- name: Set the following variables. + oc_clusterrole: + name: myclusterrole + rules: + apiGroups: + - "" + attributeRestrictions: null + verbs: [] + resources: [] +''' diff --git a/roles/lib_openshift/src/lib/clusterrole.py b/roles/lib_openshift/src/lib/clusterrole.py new file mode 100644 index 000000000..8207fbc38 --- /dev/null +++ b/roles/lib_openshift/src/lib/clusterrole.py @@ -0,0 +1,68 @@ +# pylint: skip-file +# flake8: noqa + + +# pylint: disable=too-many-public-methods +class ClusterRole(Yedit): + ''' Class to model an openshift DeploymentConfig''' + rules_path = "rules" + + def __init__(self, name=None, content=None): + ''' Constructor for clusterrole ''' + if content is None: + content = ClusterRole.builder(name).yaml_dict + + super(ClusterRole, self).__init__(content=content) + + self.__rules = Rule.parse_rules(self.get(ClusterRole.rules_path)) or [] + + @property + def rules(self): + return self.__rules + + @rules.setter + def rules(self, data): + self.__rules = data + self.put(ClusterRole.rules_path, self.__rules) + + def rule_exists(self, inc_rule): + '''attempt to find the inc_rule in the rules list''' + for rule in self.rules: + if rule == inc_rule: + return True + + return False + + def compare(self, other, verbose=False): + '''compare function for clusterrole''' + for rule in other.rules: + if rule not in self.rules: + if verbose: + print('Rule in other not found in self. [{}]'.format(rule)) + return False + + for rule in self.rules: + if rule not in other.rules: + if verbose: + print('Rule in self not found in other. [{}]'.format(rule)) + return False + + return True + + @staticmethod + def builder(name='default_clusterrole', rules=None): + '''return a clusterrole with name and/or rules''' + if rules is None: + rules = [{'apiGroups': [""], + 'attributeRestrictions': None, + 'verbs': [], + 'resources': []}] + content = { + 'apiVersion': 'v1', + 'kind': 'ClusterRole', + 'metadata': {'name': '{}'.format(name)}, + 'rules': rules, + } + + return ClusterRole(content=content) + diff --git a/roles/lib_openshift/src/lib/rule.py b/roles/lib_openshift/src/lib/rule.py new file mode 100644 index 000000000..628129832 --- /dev/null +++ b/roles/lib_openshift/src/lib/rule.py @@ -0,0 +1,143 @@ +# pylint: skip-file +# flake8: noqa + + +class Rule(object): + '''class to represent a clusterrole rule + + Example Rule Object's yaml: + - apiGroups: + - "" + attributeRestrictions: null + resources: + - persistentvolumes + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch + + ''' + def __init__(self, + api_groups=None, + attr_restrictions=None, + resources=None, + verbs=None): + self.__api_groups = api_groups if api_groups is not None else [""] + self.__verbs = verbs if verbs is not None else [] + self.__resources = resources if resources is not None else [] + self.__attribute_restrictions = attr_restrictions if attr_restrictions is not None else None + + @property + def verbs(self): + '''property for verbs''' + if self.__verbs is None: + return [] + + return self.__verbs + + @verbs.setter + def verbs(self, data): + '''setter for verbs''' + self.__verbs = data + + @property + def api_groups(self): + '''property for api_groups''' + if self.__api_groups is None: + return [] + return self.__api_groups + + @api_groups.setter + def api_groups(self, data): + '''setter for api_groups''' + self.__api_groups = data + + @property + def resources(self): + '''property for resources''' + if self.__resources is None: + return [] + + return self.__resources + + @resources.setter + def resources(self, data): + '''setter for resources''' + self.__resources = data + + @property + def attribute_restrictions(self): + '''property for attribute_restrictions''' + return self.__attribute_restrictions + + @attribute_restrictions.setter + def attribute_restrictions(self, data): + '''setter for attribute_restrictions''' + self.__attribute_restrictions = data + + def add_verb(self, inc_verb): + '''add a verb to the verbs array''' + self.verbs.append(inc_verb) + + def add_api_group(self, inc_apigroup): + '''add an api_group to the api_groups array''' + self.api_groups.append(inc_apigroup) + + def add_resource(self, inc_resource): + '''add an resource to the resources array''' + + def remove_verb(self, inc_verb): + '''add a verb to the verbs array''' + try: + self.verbs.remove(inc_verb) + return True + except ValueError: + pass + + return False + + def remove_api_group(self, inc_api_group): + '''add a verb to the verbs array''' + try: + self.api_groups.remove(inc_api_group) + return True + except ValueError: + pass + + return False + + def remove_resource(self, inc_resource): + '''add a verb to the verbs array''' + try: + self.resources.remove(inc_resource) + return True + except ValueError: + pass + + return False + + def __eq__(self, other): + '''return whether rules are equal''' + return self.attribute_restrictions == other.attribute_restrictions and \ + self.api_groups == other.api_groups and \ + self.resources == other.resources and \ + self.verbs == other.verbs + + + @staticmethod + def parse_rules(inc_rules): + '''create rules from an array''' + + results = [] + for rule in inc_rules: + results.append(Rule(rule['apiGroups'], + rule['attributeRestrictions'], + rule['resources'], + rule['verbs'])) + + return results diff --git a/roles/lib_openshift/src/sources.yml b/roles/lib_openshift/src/sources.yml index 135e2b752..9fa2a6c0e 100644 --- a/roles/lib_openshift/src/sources.yml +++ b/roles/lib_openshift/src/sources.yml @@ -89,6 +89,18 @@ oc_configmap.py: - class/oc_configmap.py - ansible/oc_configmap.py +oc_clusterrole.py: +- doc/generated +- doc/license +- lib/import.py +- doc/clusterrole +- ../../lib_utils/src/class/yedit.py +- lib/base.py +- lib/rule.py +- lib/clusterrole.py +- class/oc_clusterrole.py +- ansible/oc_clusterrole.py + oc_edit.py: - doc/generated - doc/license diff --git a/roles/lib_openshift/src/test/integration/oc_clusterrole.yml b/roles/lib_openshift/src/test/integration/oc_clusterrole.yml new file mode 100755 index 000000000..91b143f55 --- /dev/null +++ b/roles/lib_openshift/src/test/integration/oc_clusterrole.yml @@ -0,0 +1,106 @@ +#!/usr/bin/ansible-playbook --module-path=../../../library/ +## ./oc_configmap.yml -M ../../../library -e "cli_master_test=$OPENSHIFT_MASTER +--- +- hosts: "{{ cli_master_test }}" + gather_facts: no + user: root + + post_tasks: + - name: create a test project + oc_project: + name: test + description: for tests only + + ###### create test ########### + - name: create a clusterrole + oc_clusterrole: + state: present + name: operations + rules: + - apiGroups: + - "" + resources: + - persistentvolumes + attributeRestrictions: null + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch + + - name: fetch the created clusterrole + oc_clusterrole: + name: operations + state: list + register: croleout + + - debug: var=croleout + + - name: assert clusterrole exists + assert: + that: + - croleout.results.results.metadata.name == 'operations' + - croleout.results.results.rules[0].resources[0] == 'persistentvolumes' + ###### end create test ########### + + ###### update test ########### + - name: update a clusterrole + oc_clusterrole: + state: present + name: operations + rules: + - apiGroups: + - "" + resources: + - persistentvolumes + - serviceaccounts + - services + attributeRestrictions: null + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch + + - name: fetch the created clusterrole + oc_clusterrole: + name: operations + state: list + register: croleout + + - debug: var=croleout + + - name: assert clusterrole is updated + assert: + that: + - croleout.results.results.metadata.name == 'operations' + - "'persistentvolumes' in croleout.results.results.rules[0].resources" + - "'serviceaccounts' in croleout.results.results.rules[0].resources" + - "'services' in croleout.results.results.rules[0].resources" + ###### end create test ########### + + ###### delete test ########### + - name: delete a clusterrole + oc_clusterrole: + state: absent + name: operations + + - name: fetch the clusterrole + oc_clusterrole: + name: operations + state: list + register: croleout + + - debug: var=croleout + + - name: assert operations does not exist + assert: + that: "'\"operations\" not found' in croleout.results.stderr" diff --git a/roles/lib_openshift/src/test/unit/test_oc_clusterrole.py b/roles/lib_openshift/src/test/unit/test_oc_clusterrole.py new file mode 100755 index 000000000..aa4518821 --- /dev/null +++ b/roles/lib_openshift/src/test/unit/test_oc_clusterrole.py @@ -0,0 +1,116 @@ +''' + Unit tests for oc clusterrole +''' + +import copy +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,wrong-import-position +# 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_clusterrole import OCClusterRole # noqa: E402 + + +class OCClusterRoleTest(unittest.TestCase): + ''' + Test class for OCClusterRole + ''' + + # run_ansible input parameters + params = { + 'state': 'present', + 'name': 'operations', + 'rules': [ + {'apiGroups': [''], + 'attributeRestrictions': None, + 'verbs': ['create', 'delete', 'deletecollection', + 'get', 'list', 'patch', 'update', 'watch'], + 'resources': ['persistentvolumes']} + ], + 'kubeconfig': '/etc/origin/master/admin.kubeconfig', + 'debug': False, + } + + @mock.patch('oc_clusterrole.locate_oc_binary') + @mock.patch('oc_clusterrole.Utils.create_tmpfile_copy') + @mock.patch('oc_clusterrole.Utils._write') + @mock.patch('oc_clusterrole.OCClusterRole._run') + def test_adding_a_clusterrole(self, mock_cmd, mock_write, mock_tmpfile_copy, mock_loc_binary): + ''' Testing adding a project ''' + + params = copy.deepcopy(OCClusterRoleTest.params) + + clusterrole = '''{ + "apiVersion": "v1", + "kind": "ClusterRole", + "metadata": { + "creationTimestamp": "2017-03-27T14:19:09Z", + "name": "operations", + "resourceVersion": "23", + "selfLink": "/oapi/v1/clusterrolesoperations", + "uid": "57d358fe-12f8-11e7-874a-0ec502977670" + }, + "rules": [ + { + "apiGroups": [ + "" + ], + "attributeRestrictions": null, + "resources": [ + "persistentvolumes" + ], + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] + } + ] + }''' + + # Return values of our mocked function call. These get returned once per call. + mock_cmd.side_effect = [ + (1, '', 'Error from server: clusterrole "operations" not found'), + (1, '', 'Error from server: namespaces "operations" not found'), + (0, '', ''), # created + (0, clusterrole, ''), # fetch it + ] + + mock_tmpfile_copy.side_effect = [ + '/tmp/mocked_kubeconfig', + ] + + mock_loc_binary.side_effect = [ + 'oc', + ] + + # Act + results = OCClusterRole.run_ansible(params, False) + + # Assert + self.assertTrue(results['changed']) + self.assertEqual(results['results']['returncode'], 0) + self.assertEqual(results['results']['results']['metadata']['name'], 'operations') + self.assertEqual(results['state'], 'present') + + # Making sure our mock was called as we expected + mock_cmd.assert_has_calls([ + mock.call(['oc', 'get', 'clusterrole', 'operations', '-o', 'json'], None), + mock.call(['oc', 'get', 'clusterrole', 'operations', '-o', 'json'], None), + mock.call(['oc', 'create', '-f', mock.ANY], None), + mock.call(['oc', 'get', 'clusterrole', 'operations', '-o', 'json'], None), + ]) + -- cgit v1.2.3 From 9b4530d8e0be3aa96fd24cc3e7cab67257ff836d Mon Sep 17 00:00:00 2001 From: Kenny Woodson Date: Wed, 29 Mar 2017 09:25:45 -0400 Subject: Fixing linting for spaces. --- roles/lib_openshift/src/class/oc_clusterrole.py | 8 ++++---- roles/lib_openshift/src/test/unit/test_oc_clusterrole.py | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'roles/lib_openshift/src') diff --git a/roles/lib_openshift/src/class/oc_clusterrole.py b/roles/lib_openshift/src/class/oc_clusterrole.py index 0d8ed42ba..1d3d977db 100644 --- a/roles/lib_openshift/src/class/oc_clusterrole.py +++ b/roles/lib_openshift/src/class/oc_clusterrole.py @@ -82,10 +82,10 @@ class OCClusterRole(OpenShiftCLI): def run_ansible(params, check_mode): '''run the idempotent ansible code''' - oc_clusterrole = OCClusterRole(params['name'], - params['rules'], - params['kubeconfig'], - params['debug']) + oc_clusterrole = OCClusterRole(params['name'], + params['rules'], + params['kubeconfig'], + params['debug']) state = params['state'] diff --git a/roles/lib_openshift/src/test/unit/test_oc_clusterrole.py b/roles/lib_openshift/src/test/unit/test_oc_clusterrole.py index aa4518821..189f16bda 100755 --- a/roles/lib_openshift/src/test/unit/test_oc_clusterrole.py +++ b/roles/lib_openshift/src/test/unit/test_oc_clusterrole.py @@ -113,4 +113,3 @@ class OCClusterRoleTest(unittest.TestCase): mock.call(['oc', 'create', '-f', mock.ANY], None), mock.call(['oc', 'get', 'clusterrole', 'operations', '-o', 'json'], None), ]) - -- cgit v1.2.3 From a8e180943aa3bd1692507f5ecd4126b7993202c6 Mon Sep 17 00:00:00 2001 From: Kenny Woodson Date: Wed, 29 Mar 2017 11:27:30 -0400 Subject: Fixed docs. Fixed add_resource. --- roles/lib_openshift/src/lib/clusterrole.py | 2 +- roles/lib_openshift/src/lib/rule.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'roles/lib_openshift/src') diff --git a/roles/lib_openshift/src/lib/clusterrole.py b/roles/lib_openshift/src/lib/clusterrole.py index 8207fbc38..93ffababf 100644 --- a/roles/lib_openshift/src/lib/clusterrole.py +++ b/roles/lib_openshift/src/lib/clusterrole.py @@ -4,7 +4,7 @@ # pylint: disable=too-many-public-methods class ClusterRole(Yedit): - ''' Class to model an openshift DeploymentConfig''' + ''' Class to model an openshift ClusterRole''' rules_path = "rules" def __init__(self, name=None, content=None): diff --git a/roles/lib_openshift/src/lib/rule.py b/roles/lib_openshift/src/lib/rule.py index 628129832..d1e900427 100644 --- a/roles/lib_openshift/src/lib/rule.py +++ b/roles/lib_openshift/src/lib/rule.py @@ -90,6 +90,7 @@ class Rule(object): def add_resource(self, inc_resource): '''add an resource to the resources array''' + self.resources.append(inc_resource) def remove_verb(self, inc_verb): '''add a verb to the verbs array''' @@ -123,10 +124,10 @@ class Rule(object): def __eq__(self, other): '''return whether rules are equal''' - return self.attribute_restrictions == other.attribute_restrictions and \ - self.api_groups == other.api_groups and \ - self.resources == other.resources and \ - self.verbs == other.verbs + return (self.attribute_restrictions == other.attribute_restrictions and + self.api_groups == other.api_groups and + self.resources == other.resources and + self.verbs == other.verbs) @staticmethod -- cgit v1.2.3 From 44b5ab73728d569b9d77f5cc179a5b36f9d2485f Mon Sep 17 00:00:00 2001 From: Kenny Woodson Date: Wed, 29 Mar 2017 11:51:16 -0400 Subject: Fixed spacing. --- roles/lib_openshift/src/lib/rule.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'roles/lib_openshift/src') diff --git a/roles/lib_openshift/src/lib/rule.py b/roles/lib_openshift/src/lib/rule.py index d1e900427..4590dcf90 100644 --- a/roles/lib_openshift/src/lib/rule.py +++ b/roles/lib_openshift/src/lib/rule.py @@ -125,9 +125,9 @@ class Rule(object): def __eq__(self, other): '''return whether rules are equal''' return (self.attribute_restrictions == other.attribute_restrictions and - self.api_groups == other.api_groups and - self.resources == other.resources and - self.verbs == other.verbs) + self.api_groups == other.api_groups and + self.resources == other.resources and + self.verbs == other.verbs) @staticmethod -- cgit v1.2.3