summaryrefslogtreecommitdiffstats
path: root/roles/lib_openshift/src
diff options
context:
space:
mode:
authorJoel Diaz <jdiaz@redhat.com>2017-02-10 19:30:37 +0000
committerJoel Diaz <jdiaz@redhat.com>2017-03-21 17:33:27 -0400
commit45fbfdad1b80c50276a9da3841d6e4089b109e35 (patch)
treee84fc1960065337a5c5059f833b8d57d69bf8032 /roles/lib_openshift/src
parentbe09be62cf9c3ac7ef1142f494437026d72bd3e7 (diff)
downloadopenshift-45fbfdad1b80c50276a9da3841d6e4089b109e35.tar.gz
openshift-45fbfdad1b80c50276a9da3841d6e4089b109e35.tar.bz2
openshift-45fbfdad1b80c50276a9da3841d6e4089b109e35.tar.xz
openshift-45fbfdad1b80c50276a9da3841d6e4089b109e35.zip
add oc_user ansible module
module to manipulate OpenShift users, and assign group membership to users
Diffstat (limited to 'roles/lib_openshift/src')
-rw-r--r--roles/lib_openshift/src/ansible/oc_user.py34
-rw-r--r--roles/lib_openshift/src/class/oc_user.py227
-rw-r--r--roles/lib_openshift/src/doc/user66
-rw-r--r--roles/lib_openshift/src/lib/user.py37
-rw-r--r--roles/lib_openshift/src/sources.yml11
-rwxr-xr-xroles/lib_openshift/src/test/integration/oc_user.yml240
-rwxr-xr-xroles/lib_openshift/src/test/unit/oc_user.py117
7 files changed, 732 insertions, 0 deletions
diff --git a/roles/lib_openshift/src/ansible/oc_user.py b/roles/lib_openshift/src/ansible/oc_user.py
new file mode 100644
index 000000000..6b1440796
--- /dev/null
+++ b/roles/lib_openshift/src/ansible/oc_user.py
@@ -0,0 +1,34 @@
+# pylint: skip-file
+# flake8: noqa
+
+def main():
+ '''
+ ansible oc module for user
+ '''
+
+ 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'),
+ username=dict(default=None, type='str'),
+ full_name=dict(default=None, type='str'),
+ # setting groups for user data will not populate the
+ # 'groups' field in the user data.
+ # it will call out to the group data and make the user
+ # entry there
+ groups=dict(default=[], type='list'),
+ ),
+ supports_check_mode=True,
+ )
+
+ results = OCUser.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_user.py b/roles/lib_openshift/src/class/oc_user.py
new file mode 100644
index 000000000..17b679289
--- /dev/null
+++ b/roles/lib_openshift/src/class/oc_user.py
@@ -0,0 +1,227 @@
+# pylint: skip-file
+# flake8: noqa
+
+# pylint: disable=too-many-instance-attributes
+class OCUser(OpenShiftCLI):
+ ''' Class to wrap the oc command line tools '''
+ kind = 'users'
+
+ def __init__(self,
+ config,
+ groups=None,
+ verbose=False):
+ ''' Constructor for OCUser '''
+ # namespace has no meaning for user operations, hardcode to 'default'
+ super(OCUser, self).__init__('default', config.kubeconfig)
+ self.config = config
+ self.groups = groups
+ self._user = None
+
+ @property
+ def user(self):
+ ''' property function service'''
+ if not self._user:
+ self.get()
+ return self._user
+
+ @user.setter
+ def user(self, data):
+ ''' setter function for yedit var '''
+ self._user = data
+
+ def exists(self):
+ ''' return whether a user exists '''
+ if self.user:
+ return True
+
+ return False
+
+ def get(self):
+ ''' return user information '''
+ result = self._get(self.kind, self.config.username)
+ if result['returncode'] == 0:
+ self.user = User(content=result['results'][0])
+ elif 'users \"%s\" not found' % self.config.username in result['stderr']:
+ result['returncode'] = 0
+ result['results'] = [{}]
+
+ return result
+
+ def delete(self):
+ ''' delete the object '''
+ return self._delete(self.kind, self.config.username)
+
+ def create_group_entries(self):
+ ''' make entries for user to the provided group list '''
+ if self.groups != None:
+ for group in self.groups:
+ cmd = ['groups', 'add-users', group, self.config.username]
+ rval = self.openshift_cmd(cmd, oadm=True)
+ if rval['returncode'] != 0:
+ return rval
+
+ return rval
+
+ return {'returncode': 0}
+
+ def create(self):
+ ''' create the object '''
+ rval = self.create_group_entries()
+ if rval['returncode'] != 0:
+ return rval
+
+ return self._create_from_content(self.config.username, self.config.data)
+
+ def group_update(self):
+ ''' update group membership '''
+ rval = {'returncode': 0}
+ cmd = ['get', 'groups', '-o', 'json']
+ all_groups = self.openshift_cmd(cmd, output=True)
+
+ # pylint misindentifying all_groups['results']['items'] type
+ # pylint: disable=invalid-sequence-index
+ for group in all_groups['results']['items']:
+ # If we're supposed to be in this group
+ if group['metadata']['name'] in self.groups \
+ and (group['users'] is None or self.config.username not in group['users']):
+ cmd = ['groups', 'add-users', group['metadata']['name'],
+ self.config.username]
+ rval = self.openshift_cmd(cmd, oadm=True)
+ if rval['returncode'] != 0:
+ return rval
+ # else if we're in the group, but aren't supposed to be
+ elif group['users'] != None and self.config.username in group['users'] \
+ and group['metadata']['name'] not in self.groups:
+ cmd = ['groups', 'remove-users', group['metadata']['name'],
+ self.config.username]
+ rval = self.openshift_cmd(cmd, oadm=True)
+ if rval['returncode'] != 0:
+ return rval
+
+ return rval
+
+ def update(self):
+ ''' update the object '''
+ rval = self.group_update()
+ if rval['returncode'] != 0:
+ return rval
+
+ # need to update the user's info
+ return self._replace_content(self.kind, self.config.username, self.config.data, force=True)
+
+ def needs_group_update(self):
+ ''' check if there are group membership changes '''
+ cmd = ['get', 'groups', '-o', 'json']
+ all_groups = self.openshift_cmd(cmd, output=True)
+
+ # pylint misindentifying all_groups['results']['items'] type
+ # pylint: disable=invalid-sequence-index
+ for group in all_groups['results']['items']:
+ # If we're supposed to be in this group
+ if group['metadata']['name'] in self.groups \
+ and (group['users'] is None or self.config.username not in group['users']):
+ return True
+ # else if we're in the group, but aren't supposed to be
+ elif group['users'] != None and self.config.username in group['users'] \
+ and group['metadata']['name'] not in self.groups:
+ return True
+
+ return False
+
+ def needs_update(self):
+ ''' verify an update is needed '''
+ skip = []
+ if self.needs_group_update():
+ return True
+
+ return not Utils.check_def_equal(self.config.data, self.user.yaml_dict, skip_keys=skip, debug=True)
+
+ # pylint: disable=too-many-return-statements
+ @staticmethod
+ def run_ansible(params, check_mode=False):
+ ''' run the idempotent ansible code
+
+ params comes from the ansible portion of this module
+ check_mode: does the module support check mode. (module.check_mode)
+ '''
+
+ uconfig = UserConfig(params['kubeconfig'],
+ params['username'],
+ params['full_name'],
+ )
+
+ oc_user = OCUser(uconfig, params['groups'],
+ verbose=params['debug'])
+ state = params['state']
+
+ api_rval = oc_user.get()
+
+ #####
+ # Get
+ #####
+ if state == 'list':
+ return {'changed': False, 'results': api_rval['results'], 'state': "list"}
+
+ ########
+ # Delete
+ ########
+ if state == 'absent':
+ if oc_user.exists():
+
+ if check_mode:
+ return {'changed': False, 'msg': 'Would have performed a delete.'}
+
+ api_rval = oc_user.delete()
+
+ return {'changed': True, 'results': api_rval, 'state': "absent"}
+ return {'changed': False, 'state': "absent"}
+
+ if state == 'present':
+ ########
+ # Create
+ ########
+ if not oc_user.exists():
+
+ if check_mode:
+ return {'changed': False, 'msg': 'Would have performed a create.'}
+
+ # Create it here
+ api_rval = oc_user.create()
+
+ if api_rval['returncode'] != 0:
+ return {'failed': True, 'msg': api_rval}
+
+ # return the created object
+ api_rval = oc_user.get()
+
+ if api_rval['returncode'] != 0:
+ return {'failed': True, 'msg': api_rval}
+
+ return {'changed': True, 'results': api_rval, 'state': "present"}
+
+ ########
+ # Update
+ ########
+ if oc_user.needs_update():
+ api_rval = oc_user.update()
+
+ if api_rval['returncode'] != 0:
+ return {'failed': True, 'msg': api_rval}
+
+ orig_cmd = api_rval['cmd']
+ # return the created object
+ api_rval = oc_user.get()
+ # overwrite the get/list cmd
+ api_rval['cmd'] = orig_cmd
+
+ if api_rval['returncode'] != 0:
+ return {'failed': True, 'msg': api_rval}
+
+ return {'changed': True, 'results': api_rval, 'state': "present"}
+
+ return {'changed': False, 'results': api_rval, 'state': "present"}
+
+ return {'failed': True,
+ 'changed': False,
+ 'results': 'Unknown state passed. %s' % state,
+ 'state': "unknown"}
diff --git a/roles/lib_openshift/src/doc/user b/roles/lib_openshift/src/doc/user
new file mode 100644
index 000000000..cae108415
--- /dev/null
+++ b/roles/lib_openshift/src/doc/user
@@ -0,0 +1,66 @@
+# flake8: noqa
+# pylint: skip-file
+
+DOCUMENTATION = '''
+---
+module: oc_user
+short_description: Create, modify, and idempotently manage openshift users.
+description:
+ - Modify openshift users programmatically.
+options:
+ state:
+ description:
+ - State controls the action that will be taken with resource
+ - 'present' will create or update a user to the desired state
+ - 'absent' will ensure user is removed
+ - 'list' will read and return a list of users
+ 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: []
+ username:
+ description:
+ - Short username to query/modify.
+ required: false
+ default: None
+ aliases: []
+ full_name:
+ description:
+ - String with the full name/description of th user.
+ required: false
+ default: None
+ aliases: []
+ groups:
+ description:
+ - List of groups the user should be a member of.
+ required: false
+ default: []
+ aliases: []
+author:
+- "Joel Diaz <jdiaz@redhat.com>"
+extends_documentation_fragment: []
+'''
+
+EXAMPLES = '''
+- name: Ensure user exists
+ oc_user:
+ state: present
+ username: johndoe
+ full_name "John Doe"
+
+- name: Ensure user does not exist
+ oc_user:
+ state: absent
+ username: johndoe
+'''
diff --git a/roles/lib_openshift/src/lib/user.py b/roles/lib_openshift/src/lib/user.py
new file mode 100644
index 000000000..a14d5fc91
--- /dev/null
+++ b/roles/lib_openshift/src/lib/user.py
@@ -0,0 +1,37 @@
+# pylint: skip-file
+# flake8: noqa
+
+
+class UserConfig(object):
+ ''' Handle user options '''
+ def __init__(self,
+ kubeconfig,
+ username,
+ full_name):
+ ''' constructor for handling user options '''
+ self.kubeconfig = kubeconfig
+ self.username = username
+ self.full_name = full_name
+
+ self.data = {}
+ self.create_dict()
+
+ def create_dict(self):
+ ''' return a user as a dict '''
+ self.data['apiVersion'] = 'v1'
+ self.data['fullName'] = self.full_name
+ self.data['groups'] = None
+ self.data['identities'] = None
+ self.data['kind'] = 'User'
+ self.data['metadata'] = {}
+ self.data['metadata']['name'] = self.username
+
+
+# pylint: disable=too-many-instance-attributes
+class User(Yedit):
+ ''' Class to wrap the oc command line tools '''
+ kind = 'user'
+
+ def __init__(self, content):
+ '''User constructor'''
+ super(User, self).__init__(content=content)
diff --git a/roles/lib_openshift/src/sources.yml b/roles/lib_openshift/src/sources.yml
index 91ee86750..a7204dce1 100644
--- a/roles/lib_openshift/src/sources.yml
+++ b/roles/lib_openshift/src/sources.yml
@@ -219,6 +219,17 @@ oc_service.py:
- class/oc_service.py
- ansible/oc_service.py
+oc_user.py:
+- doc/generated
+- doc/license
+- lib/import.py
+- doc/user
+- ../../lib_utils/src/class/yedit.py
+- lib/base.py
+- lib/user.py
+- class/oc_user.py
+- ansible/oc_user.py
+
oc_version.py:
- doc/generated
- doc/license
diff --git a/roles/lib_openshift/src/test/integration/oc_user.yml b/roles/lib_openshift/src/test/integration/oc_user.yml
new file mode 100755
index 000000000..7d6221e64
--- /dev/null
+++ b/roles/lib_openshift/src/test/integration/oc_user.yml
@@ -0,0 +1,240 @@
+#!/usr/bin/ansible-playbook --module-path=../../../library/
+#
+# ./oc_user.yml -e "cli_master_test=$OPENSHIFT_MASTER
+#
+---
+- hosts: "{{ cli_master_test }}"
+ gather_facts: no
+ user: root
+
+ vars:
+ test_user: testuser@email.com
+ test_user_fullname: "Test User"
+ pre_tasks:
+ - name: ensure needed vars are defined
+ fail:
+ msg: "{{ item }} no defined"
+ when: "{{ item}} is not defined"
+ with_items:
+ - cli_master_test # ansible inventory instance to run playbook against
+
+ tasks:
+ - name: delete test user (so future tests work)
+ oc_user:
+ state: absent
+ username: "{{ test_user }}"
+
+ - name: get user list
+ oc_user:
+ state: list
+ username: "{{ test_user }}"
+ register: user_out
+ - name: "assert test user does not exist"
+ assert:
+ that: user_out['results'][0] == {}
+ msg: "{{ user_out }}"
+
+ - name: get all list
+ oc_user:
+ state: list
+ register: user_out
+ #- debug: var=user_out
+
+ - name: add test user
+ oc_user:
+ state: present
+ username: "{{ test_user }}"
+ full_name: "{{ test_user_fullname }}"
+ register: user_out
+ - name: assert result set to changed
+ assert:
+ that: user_out['changed'] == True
+ msg: "{{ user_out }}"
+
+ - name: check test user actually added
+ oc_user:
+ state: list
+ username: "{{ test_user }}"
+ register: user_out
+ - name: assert user actually added
+ assert:
+ that: user_out['results'][0]['metadata']['name'] == "{{ test_user }}" and
+ user_out['results'][0]['fullName'] == "{{ test_user_fullname }}"
+ msg: "{{ user_out }}"
+
+ - name: re-add test user
+ oc_user:
+ state: present
+ username: "{{ test_user }}"
+ full_name: "{{ test_user_fullname }}"
+ register: user_out
+ - name: assert re-add result set to not changed
+ assert:
+ that: user_out['changed'] == False
+ msg: "{{ user_out }}"
+
+ - name: modify existing user
+ oc_user:
+ state: present
+ username: "{{ test_user }}"
+ full_name: 'Something Different'
+ register: user_out
+ - name: assert modify existing user result set to changed
+ assert:
+ that: user_out['changed'] == True
+ msg: "{{ user_out }}"
+
+ - name: check modify test user
+ oc_user:
+ state: list
+ username: "{{ test_user }}"
+ register: user_out
+ - name: assert modification successful
+ assert:
+ that: user_out['results'][0]['metadata']['name'] == "{{ test_user }}" and
+ user_out['results'][0]['fullName'] == 'Something Different'
+ msg: "{{ user_out }}"
+
+ - name: delete test user
+ oc_user:
+ state: absent
+ username: "{{ test_user }}"
+ register: user_out
+ - name: assert delete marked changed
+ assert:
+ that: user_out['changed'] == True
+ msg: "{{ user_out }}"
+
+ - name: check delete user
+ oc_user:
+ state: list
+ username: "{{ test_user }}"
+ register: user_out
+ - name: assert deletion successful
+ assert:
+ that: user_out['results'][0] == {}
+ msg: "{{ user_out }}"
+
+ - name: re-delete test user
+ oc_user:
+ state: absent
+ username: "{{ test_user }}"
+ register: user_out
+ - name: check re-delete marked not changed
+ assert:
+ that: user_out['changed'] == False
+ msg: "{{ user_out }}"
+
+ - name: delete test group
+ oc_obj:
+ kind: group
+ state: absent
+ name: integration-test-group
+
+ - name: create test group
+ command: oadm groups new integration-test-group
+
+ - name: check group creation
+ oc_obj:
+ kind: group
+ state: list
+ name: integration-test-group
+ register: user_out
+ - name: assert test group created
+ assert:
+ that: user_out['results'][0]['metadata']['name'] == "integration-test-group" and
+ user_out['results'][0]['users'] is not defined
+
+ - name: create user with group membership
+ oc_user:
+ state: present
+ username: "{{ test_user }}"
+ groups:
+ - "integration-test-group"
+ register: user_out
+ - debug: var=user_out
+ - name: get group user members
+ oc_obj:
+ kind: group
+ state: list
+ name: integration-test-group
+ register: user_out
+ - name: assert user group membership
+ assert:
+ that: "'{{ test_user }}' in user_out['results']['results'][0]['users'][0]"
+ msg: "{{ user_out }}"
+
+ - name: delete second test group
+ oc_obj:
+ kind: group
+ state: absent
+ name: integration-test-group2
+
+ - name: create empty second group
+ command: oadm groups new integration-test-group2
+
+ - name: update user with second group membership
+ oc_user:
+ state: present
+ username: "{{ test_user }}"
+ groups:
+ - "integration-test-group"
+ - "integration-test-group2"
+ register: user_out
+ - name: assert adding more group changed
+ assert:
+ that: user_out['changed'] == True
+
+ - name: get group memberships
+ oc_obj:
+ kind: group
+ state: list
+ name: "{{ item }}"
+ with_items:
+ - integration-test-group
+ - integration-test-group2
+ register: user_out
+ - name: assert user member of above groups
+ assert:
+ that: "'{{ test_user }}' in user_out['results'][0]['results']['results'][0]['users'] and \
+ '{{ test_user }}' in user_out['results'][1]['results']['results'][0]['users']"
+ msg: "{{ user_out }}"
+
+ - name: update user with only one group
+ oc_user:
+ state: present
+ username: "{{ test_user }}"
+ groups:
+ - "integration-test-group2"
+ register: user_out
+ - assert:
+ that: user_out['changed'] == True
+
+ - name: get group memberships
+ oc_obj:
+ kind: group
+ state: list
+ name: "{{ item }}"
+ with_items:
+ - "integration-test-group"
+ - "integration-test-group2"
+ register: user_out
+ - debug: var=user_out
+ - name: assert proper user membership
+ assert:
+ that: "'{{ test_user }}' not in user_out['results'][0]['results']['results'][0]['users'] and \
+ '{{ test_user }}' in user_out['results'][1]['results']['results'][0]['users']"
+
+ - name: clean up test groups
+ oc_obj:
+ kind: group
+ state: absent
+ name: "{{ item }}"
+ with_items:
+ - "integration-test-group"
+ - "integration-test-group2"
+
+ - name: clean up test user
+ oc_user:
+ state: absent
+ username: "{{ test_user }}"
diff --git a/roles/lib_openshift/src/test/unit/oc_user.py b/roles/lib_openshift/src/test/unit/oc_user.py
new file mode 100755
index 000000000..920b06d0e
--- /dev/null
+++ b/roles/lib_openshift/src/test/unit/oc_user.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python2
+'''
+ Unit tests for oc user
+'''
+# To run
+# ./oc_user.py
+#
+# ..
+# ----------------------------------------------------------------------
+# Ran 2 tests in 0.003s
+#
+# 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_user import OCUser # noqa: E402
+
+
+class OCUserTest(unittest.TestCase):
+ '''
+ Test class for OCUser
+ '''
+
+ def setUp(self):
+ ''' setup method will create a file and set to known configuration '''
+ pass
+
+ @mock.patch('oc_user.OCUser._run')
+ def test_state_list(self, mock_cmd):
+ ''' Testing a user list '''
+ params = {'username': 'testuser@email.com',
+ 'state': 'list',
+ 'kubeconfig': '/etc/origin/master/admin.kubeconfig',
+ 'full_name': None,
+ 'groups': [],
+ 'debug': False}
+
+ user = '''{
+ "kind": "User",
+ "apiVersion": "v1",
+ "metadata": {
+ "name": "testuser@email.com",
+ "selfLink": "/oapi/v1/users/testuser@email.com",
+ "uid": "02fee6c9-f20d-11e6-b83b-12e1a7285e80",
+ "resourceVersion": "38566887",
+ "creationTimestamp": "2017-02-13T16:53:58Z"
+ },
+ "fullName": "Test User",
+ "identities": null,
+ "groups": null
+ }'''
+
+ mock_cmd.side_effect = [
+ (0, user, ''),
+ ]
+
+ results = OCUser.run_ansible(params, False)
+
+ self.assertFalse(results['changed'])
+ self.assertTrue(results['results'][0]['metadata']['name'] == "testuser@email.com")
+
+ @mock.patch('oc_user.OCUser._run')
+ def test_state_present(self, mock_cmd):
+ ''' Testing a user list '''
+ params = {'username': 'testuser@email.com',
+ 'state': 'present',
+ 'kubeconfig': '/etc/origin/master/admin.kubeconfig',
+ 'full_name': 'Test User',
+ 'groups': [],
+ 'debug': False}
+
+ created_user = '''{
+ "kind": "User",
+ "apiVersion": "v1",
+ "metadata": {
+ "name": "testuser@email.com",
+ "selfLink": "/oapi/v1/users/testuser@email.com",
+ "uid": "8d508039-f224-11e6-b83b-12e1a7285e80",
+ "resourceVersion": "38646241",
+ "creationTimestamp": "2017-02-13T19:42:28Z"
+ },
+ "fullName": "Test User",
+ "identities": null,
+ "groups": null
+ }'''
+
+ mock_cmd.side_effect = [
+ (1, '', 'Error from server: users "testuser@email.com" not found'), # get
+ (1, '', 'Error from server: users "testuser@email.com" not found'), # get
+ (0, 'user "testuser@email.com" created', ''), # create
+ (0, created_user, ''), # get
+ ]
+
+ results = OCUser.run_ansible(params, False)
+
+ self.assertTrue(results['changed'])
+ self.assertTrue(results['results']['results'][0]['metadata']['name'] ==
+ "testuser@email.com")
+
+ def tearDown(self):
+ '''TearDown method'''
+ pass
+
+
+if __name__ == "__main__":
+ unittest.main()