From 611e9864e8716ca92a77745403c847c30f2f0d87 Mon Sep 17 00:00:00 2001
From: Kenny Woodson <kwoodson@redhat.com>
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 <kwoodson@redhat.com>"
+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