From 873ec7cda3edb0f8f479da257de168976e386f77 Mon Sep 17 00:00:00 2001 From: Kenny Woodson Date: Mon, 20 Mar 2017 19:25:33 -0400 Subject: Adding oc_configmap to lib_openshift. --- roles/lib_openshift/src/ansible/oc_configmap.py | 32 +++ roles/lib_openshift/src/class/oc_configmap.py | 183 ++++++++++++++++ roles/lib_openshift/src/doc/configmap | 72 +++++++ roles/lib_openshift/src/sources.yml | 10 + .../src/test/integration/oc_configmap.yml | 94 ++++++++ .../src/test/unit/test_oc_configmap.py | 239 +++++++++++++++++++++ 6 files changed, 630 insertions(+) create mode 100644 roles/lib_openshift/src/ansible/oc_configmap.py create mode 100644 roles/lib_openshift/src/class/oc_configmap.py create mode 100644 roles/lib_openshift/src/doc/configmap create mode 100755 roles/lib_openshift/src/test/integration/oc_configmap.yml create mode 100755 roles/lib_openshift/src/test/unit/test_oc_configmap.py (limited to 'roles/lib_openshift/src') diff --git a/roles/lib_openshift/src/ansible/oc_configmap.py b/roles/lib_openshift/src/ansible/oc_configmap.py new file mode 100644 index 000000000..659e5cb8d --- /dev/null +++ b/roles/lib_openshift/src/ansible/oc_configmap.py @@ -0,0 +1,32 @@ +# pylint: skip-file +# flake8: noqa + + +def main(): + ''' + ansible oc module for managing OpenShift configmap objects + ''' + + 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'), + namespace=dict(default='default', type='str'), + name=dict(default=None, type='str'), + from_file=dict(default=None, type='dict'), + from_literal=dict(default=None, type='dict'), + ), + supports_check_mode=True, + ) + + + rval = OCConfigMap.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_configmap.py b/roles/lib_openshift/src/class/oc_configmap.py new file mode 100644 index 000000000..c034ead36 --- /dev/null +++ b/roles/lib_openshift/src/class/oc_configmap.py @@ -0,0 +1,183 @@ +# pylint: skip-file +# flake8: noqa + + +# pylint: disable=too-many-arguments +class OCConfigMap(OpenShiftCLI): + ''' Openshift ConfigMap Class + + ConfigMaps are a way to store data inside of objects + ''' + def __init__(self, + name, + from_file, + from_literal, + state, + namespace, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False): + ''' Constructor for OpenshiftOC ''' + super(OCConfigMap, self).__init__(namespace, kubeconfig=kubeconfig, verbose=verbose) + self.name = name + self.state = state + self._configmap = None + self._inc_configmap = None + self.from_file = from_file if from_file is not None else {} + self.from_literal = from_literal if from_literal is not None else {} + + @property + def configmap(self): + if self._configmap is None: + self._configmap = self.get() + + return self._configmap + + @configmap.setter + def configmap(self, inc_map): + self._configmap = inc_map + + @property + def inc_configmap(self): + if self._inc_configmap is None: + results = self.create(dryrun=True, output=True) + self._inc_configmap = results['results'] + + return self._inc_configmap + + @inc_configmap.setter + def inc_configmap(self, inc_map): + self._inc_configmap = inc_map + + def from_file_to_params(self): + '''return from_files in a string ready for cli''' + return ["--from-file={}={}".format(key, value) for key, value in self.from_file.items()] + + def from_literal_to_params(self): + '''return from_literal in a string ready for cli''' + return ["--from-literal={}={}".format(key, value) for key, value in self.from_literal.items()] + + def get(self): + '''return a configmap by name ''' + results = self._get('configmap', self.name) + if results['returncode'] == 0 and results['results'][0]: + self.configmap = results['results'][0] + + if results['returncode'] != 0 and '"{}" not found'.format(self.name) in results['stderr']: + results['returncode'] = 0 + + return results + + def delete(self): + '''delete a configmap by name''' + return self._delete('configmap', self.name) + + def create(self, dryrun=False, output=False): + '''Create a configmap + + :dryrun: Product what you would have done. default: False + :output: Whether to parse output. default: False + ''' + + cmd = ['create', 'configmap', self.name] + if self.from_literal is not None: + cmd.extend(self.from_literal_to_params()) + + if self.from_file is not None: + cmd.extend(self.from_file_to_params()) + + if dryrun: + cmd.extend(['--dry-run', '-ojson']) + + results = self.openshift_cmd(cmd, output=output) + + return results + + def update(self): + '''run update configmap ''' + return self._replace_content('configmap', self.name, self.inc_configmap) + + def needs_update(self): + '''compare the current configmap with the proposed and return if they are equal''' + return not Utils.check_def_equal(self.inc_configmap, self.configmap, debug=self.verbose) + + @staticmethod + # pylint: disable=too-many-return-statements,too-many-branches + # TODO: This function should be refactored into its individual parts. + def run_ansible(params, check_mode): + '''run the ansible idempotent code''' + + oc_cm = OCConfigMap(params['name'], + params['from_file'], + params['from_literal'], + params['state'], + params['namespace'], + kubeconfig=params['kubeconfig'], + verbose=params['debug']) + + state = params['state'] + + api_rval = oc_cm.get() + + if 'failed' in api_rval: + return {'failed': True, 'msg': api_rval} + + ##### + # Get + ##### + if state == 'list': + return {'changed': False, 'results': api_rval, 'state': state} + + ######## + # Delete + ######## + if state == 'absent': + if not Utils.exists(api_rval['results'], params['name']): + return {'changed': False, 'state': 'absent'} + + if check_mode: + return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a delete.'} + + api_rval = oc_cm.delete() + return {'changed': True, 'results': api_rval, 'state': state} + + ######## + # Create + ######## + if state == 'present': + if not Utils.exists(api_rval['results'], params['name']): + + if check_mode: + return {'changed': True, 'msg': 'Would have performed a create.'} + + api_rval = oc_cm.create() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + api_rval = oc_cm.get() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + return {'changed': True, 'results': api_rval, 'state': state} + + ######## + # Update + ######## + if oc_cm.needs_update(): + + api_rval = oc_cm.update() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + api_rval = oc_cm.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, 'msg': 'Unknown state passed. {}'.format(state)} diff --git a/roles/lib_openshift/src/doc/configmap b/roles/lib_openshift/src/doc/configmap new file mode 100644 index 000000000..8ac4a5629 --- /dev/null +++ b/roles/lib_openshift/src/doc/configmap @@ -0,0 +1,72 @@ +# flake8: noqa +# pylint: skip-file + +DOCUMENTATION = ''' +--- +module: oc_configmap +short_description: Modify, and idempotently manage openshift configmaps +description: + - Modify openshift configmaps programmatically. +options: + state: + description: + - Supported states, present, absent, list + - present - will ensure object is created or updated to the value specified + - list - will return a configmap + - absent - will remove the configmap + 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: [] + namespace: + description: + - The namespace where the object lives. + required: false + default: str + aliases: [] + from_file: + description: + - A dict of key, value pairs representing the configmap key and the value represents the file path. + required: false + default: None + aliases: [] + from_literal: + description: + - A dict of key, value pairs representing the configmap key and the value represents the string content + required: false + default: None + aliases: [] +author: +- "kenny woodson " +extends_documentation_fragment: [] +''' + +EXAMPLES = ''' +- name: create group + oc_configmap: + state: present + name: testmap + from_file: + secret: /path/to/secret + from_literal: + title: systemadmin + register: configout +''' diff --git a/roles/lib_openshift/src/sources.yml b/roles/lib_openshift/src/sources.yml index 91ee86750..69ef02689 100644 --- a/roles/lib_openshift/src/sources.yml +++ b/roles/lib_openshift/src/sources.yml @@ -79,6 +79,16 @@ oc_atomic_container.py: - doc/atomic_container - ansible/oc_atomic_container.py +oc_configmap.py: +- doc/generated +- doc/license +- lib/import.py +- doc/configmap +- ../../lib_utils/src/class/yedit.py +- lib/base.py +- class/oc_configmap.py +- ansible/oc_configmap.py + oc_edit.py: - doc/generated - doc/license diff --git a/roles/lib_openshift/src/test/integration/oc_configmap.yml b/roles/lib_openshift/src/test/integration/oc_configmap.yml new file mode 100755 index 000000000..7e37e7fd3 --- /dev/null +++ b/roles/lib_openshift/src/test/integration/oc_configmap.yml @@ -0,0 +1,94 @@ +#!/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 + vars: + filename: /tmp/test_configmap_from_file + + post_tasks: + - name: Setup a file with known contents + copy: + content: This is a file + dest: "{{ filename }}" + + - name: create a test project + oc_project: + name: test + description: for tests only + + ###### create test ########### + - name: create a configmap + oc_configmap: + state: present + name: configmaptest + namespace: test + from_file: + config: "{{ filename }}" + from_literal: + foo: bar + + - name: fetch the created configmap + oc_configmap: + name: configmaptest + state: list + namespace: test + register: cmout + + - debug: var=cmout + + - name: assert configmaptest exists + assert: + that: + - cmout.results.results[0].metadata.name == 'configmaptest' + - cmout.results.results[0].data.foo == 'bar' + ###### end create test ########### + + ###### update test ########### + - name: create a configmap + oc_configmap: + state: present + name: configmaptest + namespace: test + from_file: + config: "{{ filename }}" + from_literal: + foo: bar + deployment_type: online + + - name: fetch the updated configmap + oc_configmap: + name: configmaptest + state: list + namespace: test + register: cmout + + - debug: var=cmout + + - name: assert configmaptest exists + assert: + that: + - cmout.results.results[0].metadata.name == 'configmaptest' + - cmout.results.results[0].data.deployment_type == 'online' + ###### end create test ########### + + ###### delete test ########### + - name: delete a configmap + oc_configmap: + state: absent + name: configmaptest + namespace: test + + - name: fetch the updated configmap + oc_configmap: + name: configmaptest + state: list + namespace: test + register: cmout + + - debug: var=cmout + + - name: assert configmaptest exists + assert: + that: "'\"configmaptest\" not found' in cmout.results.stderr" diff --git a/roles/lib_openshift/src/test/unit/test_oc_configmap.py b/roles/lib_openshift/src/test/unit/test_oc_configmap.py new file mode 100755 index 000000000..318fd6167 --- /dev/null +++ b/roles/lib_openshift/src/test/unit/test_oc_configmap.py @@ -0,0 +1,239 @@ +''' + Unit tests for oc configmap +''' + +import copy +import os +import six +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_configmap import OCConfigMap, locate_oc_binary # noqa: E402 + + +class OCConfigMapTest(unittest.TestCase): + ''' + Test class for OCConfigMap + ''' + params = {'kubeconfig': '/etc/origin/master/admin.kubeconfig', + 'state': 'present', + 'debug': False, + 'name': 'configmap', + 'from_file': {}, + 'from_literal': {}, + 'namespace': 'test'} + + @mock.patch('oc_configmap.Utils._write') + @mock.patch('oc_configmap.Utils.create_tmpfile_copy') + @mock.patch('oc_configmap.OCConfigMap._run') + def test_create_configmap(self, mock_run, mock_tmpfile_copy, mock_write): + ''' Testing a configmap create ''' + # TODO + return + params = copy.deepcopy(OCConfigMapTest.params) + params['from_file'] = {'test': '/root/file'} + params['from_literal'] = {'foo': 'bar'} + + configmap = '''{ + "apiVersion": "v1", + "data": { + "foo": "bar", + "test": "this is a file\\n" + }, + "kind": "ConfigMap", + "metadata": { + "creationTimestamp": "2017-03-20T20:24:35Z", + "name": "configmap", + "namespace": "test" + } + }''' + + mock_run.side_effect = [ + (1, '', 'Error from server (NotFound): configmaps "configmap" not found'), + (0, '', ''), + (0, configmap, ''), + ] + + mock_tmpfile_copy.side_effect = [ + '/tmp/mocked_kubeconfig', + ] + + results = OCConfigMap.run_ansible(params, False) + + self.assertTrue(results['changed']) + self.assertEqual(results['results']['results'][0]['metadata']['name'], 'configmap') + + @mock.patch('oc_configmap.Utils._write') + @mock.patch('oc_configmap.Utils.create_tmpfile_copy') + @mock.patch('oc_configmap.OCConfigMap._run') + def test_update_configmap(self, mock_run, mock_tmpfile_copy, mock_write): + ''' Testing a configmap create ''' + params = copy.deepcopy(OCConfigMapTest.params) + params['from_file'] = {'test': '/root/file'} + params['from_literal'] = {'foo': 'bar', 'deployment_type': 'online'} + + configmap = '''{ + "apiVersion": "v1", + "data": { + "foo": "bar", + "test": "this is a file\\n" + }, + "kind": "ConfigMap", + "metadata": { + "creationTimestamp": "2017-03-20T20:24:35Z", + "name": "configmap", + "namespace": "test" + + } + }''' + + mod_configmap = '''{ + "apiVersion": "v1", + "data": { + "foo": "bar", + "deployment_type": "online", + "test": "this is a file\\n" + }, + "kind": "ConfigMap", + "metadata": { + "creationTimestamp": "2017-03-20T20:24:35Z", + "name": "configmap", + "namespace": "test" + + } + }''' + + mock_run.side_effect = [ + (0, configmap, ''), + (0, mod_configmap, ''), + (0, configmap, ''), + (0, '', ''), + (0, mod_configmap, ''), + ] + + mock_tmpfile_copy.side_effect = [ + '/tmp/mocked_kubeconfig', + ] + + results = OCConfigMap.run_ansible(params, False) + + self.assertTrue(results['changed']) + self.assertEqual(results['results']['results'][0]['metadata']['name'], 'configmap') + self.assertEqual(results['results']['results'][0]['data']['deployment_type'], 'online') + + @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 From 5e06d9e3b4289be02dfd7a4d61366db95dfc0691 Mon Sep 17 00:00:00 2001 From: Kenny Woodson Date: Tue, 21 Mar 2017 12:24:50 -0400 Subject: Fixed doc. Updated test to change existing key. Updated module spec for required name param. --- roles/lib_openshift/src/ansible/oc_configmap.py | 2 +- roles/lib_openshift/src/doc/configmap | 4 ++-- roles/lib_openshift/src/test/integration/oc_configmap.yml | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) (limited to 'roles/lib_openshift/src') diff --git a/roles/lib_openshift/src/ansible/oc_configmap.py b/roles/lib_openshift/src/ansible/oc_configmap.py index 659e5cb8d..974f72499 100644 --- a/roles/lib_openshift/src/ansible/oc_configmap.py +++ b/roles/lib_openshift/src/ansible/oc_configmap.py @@ -14,7 +14,7 @@ def main(): choices=['present', 'absent', 'list']), debug=dict(default=False, type='bool'), namespace=dict(default='default', type='str'), - name=dict(default=None, type='str'), + name=dict(default=None, required=True, type='str'), from_file=dict(default=None, type='dict'), from_literal=dict(default=None, type='dict'), ), diff --git a/roles/lib_openshift/src/doc/configmap b/roles/lib_openshift/src/doc/configmap index 8ac4a5629..5ca8292c4 100644 --- a/roles/lib_openshift/src/doc/configmap +++ b/roles/lib_openshift/src/doc/configmap @@ -33,14 +33,14 @@ options: name: description: - Name of the object that is being queried. - required: false + required: True default: None aliases: [] namespace: description: - The namespace where the object lives. required: false - default: str + default: default aliases: [] from_file: description: diff --git a/roles/lib_openshift/src/test/integration/oc_configmap.yml b/roles/lib_openshift/src/test/integration/oc_configmap.yml index 7e37e7fd3..9153830aa 100755 --- a/roles/lib_openshift/src/test/integration/oc_configmap.yml +++ b/roles/lib_openshift/src/test/integration/oc_configmap.yml @@ -54,7 +54,7 @@ from_file: config: "{{ filename }}" from_literal: - foo: bar + foo: notbar deployment_type: online - name: fetch the updated configmap @@ -71,6 +71,7 @@ that: - cmout.results.results[0].metadata.name == 'configmaptest' - cmout.results.results[0].data.deployment_type == 'online' + - cmout.results.results[0].data.foo == 'notbar' ###### end create test ########### ###### delete test ########### -- cgit v1.2.3 From d28ae1f745aaedccdb1dc6a168a28accf643f15d Mon Sep 17 00:00:00 2001 From: Kenny Woodson Date: Tue, 21 Mar 2017 13:34:06 -0400 Subject: Updated comment. --- roles/lib_openshift/src/test/integration/oc_configmap.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'roles/lib_openshift/src') diff --git a/roles/lib_openshift/src/test/integration/oc_configmap.yml b/roles/lib_openshift/src/test/integration/oc_configmap.yml index 9153830aa..c0d200e73 100755 --- a/roles/lib_openshift/src/test/integration/oc_configmap.yml +++ b/roles/lib_openshift/src/test/integration/oc_configmap.yml @@ -72,7 +72,7 @@ - cmout.results.results[0].metadata.name == 'configmaptest' - cmout.results.results[0].data.deployment_type == 'online' - cmout.results.results[0].data.foo == 'notbar' - ###### end create test ########### + ###### end update test ########### ###### delete test ########### - name: delete a configmap -- cgit v1.2.3 From 3d484dd5e5ac4a8ba0c4b50baba79d6d7e5bba7e Mon Sep 17 00:00:00 2001 From: Kenny Woodson Date: Tue, 21 Mar 2017 15:53:01 -0400 Subject: Adding error checking to the delete. --- roles/lib_openshift/src/class/oc_configmap.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'roles/lib_openshift/src') diff --git a/roles/lib_openshift/src/class/oc_configmap.py b/roles/lib_openshift/src/class/oc_configmap.py index c034ead36..87de3e1df 100644 --- a/roles/lib_openshift/src/class/oc_configmap.py +++ b/roles/lib_openshift/src/class/oc_configmap.py @@ -138,6 +138,10 @@ class OCConfigMap(OpenShiftCLI): return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a delete.'} api_rval = oc_cm.delete() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + return {'changed': True, 'results': api_rval, 'state': state} ######## -- cgit v1.2.3