summaryrefslogtreecommitdiffstats
path: root/roles/lib_openshift/src
diff options
context:
space:
mode:
Diffstat (limited to 'roles/lib_openshift/src')
-rw-r--r--roles/lib_openshift/src/ansible/oc_project.py33
-rw-r--r--roles/lib_openshift/src/class/oc_project.py185
-rw-r--r--roles/lib_openshift/src/doc/project81
-rw-r--r--roles/lib_openshift/src/lib/project.py85
-rw-r--r--roles/lib_openshift/src/sources.yml11
-rwxr-xr-xroles/lib_openshift/src/test/integration/oc_project.yml83
-rwxr-xr-xroles/lib_openshift/src/test/unit/test_oc_project.py130
7 files changed, 608 insertions, 0 deletions
diff --git a/roles/lib_openshift/src/ansible/oc_project.py b/roles/lib_openshift/src/ansible/oc_project.py
new file mode 100644
index 000000000..b035cd712
--- /dev/null
+++ b/roles/lib_openshift/src/ansible/oc_project.py
@@ -0,0 +1,33 @@
+# pylint: skip-file
+# flake8: noqa
+
+def main():
+ '''
+ ansible oc module for project
+ '''
+
+ 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, require=True, type='str'),
+ display_name=dict(default=None, type='str'),
+ node_selector=dict(default=None, type='list'),
+ description=dict(default=None, type='str'),
+ admin=dict(default=None, type='str'),
+ admin_role=dict(default='admin', type='str'),
+ ),
+ supports_check_mode=True,
+ )
+
+ rval = OCProject.run_ansible(module.params, module.check_mode)
+ if 'failed' in rval:
+ return module.fail_json(**rval)
+
+ return module.exit_json(**rval)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/roles/lib_openshift/src/class/oc_project.py b/roles/lib_openshift/src/class/oc_project.py
new file mode 100644
index 000000000..7e3984297
--- /dev/null
+++ b/roles/lib_openshift/src/class/oc_project.py
@@ -0,0 +1,185 @@
+# pylint: skip-file
+# flake8: noqa
+
+
+# pylint: disable=too-many-instance-attributes
+class OCProject(OpenShiftCLI):
+ ''' Project Class to manage project/namespace objects'''
+ kind = 'namespace'
+
+ def __init__(self,
+ config,
+ verbose=False):
+ ''' Constructor for OCProject '''
+ super(OCProject, self).__init__(None, config.kubeconfig)
+ self.config = config
+ self._project = None
+
+ @property
+ def project(self):
+ ''' property for project'''
+ if not self._project:
+ self.get()
+ return self._project
+
+ @project.setter
+ def project(self, data):
+ ''' setter function for project propeorty'''
+ self._project = data
+
+ def exists(self):
+ ''' return whether a project exists '''
+ if self.project:
+ return True
+
+ return False
+
+ def get(self):
+ '''return project '''
+ result = self._get(self.kind, self.config.name)
+
+ if result['returncode'] == 0:
+ self.project = Project(content=result['results'][0])
+ result['results'] = self.project.yaml_dict
+
+ elif 'namespaces "%s" not found' % self.config.name in result['stderr']:
+ result = {'results': [], 'returncode': 0}
+
+ return result
+
+ def delete(self):
+ '''delete the object'''
+ return self._delete(self.kind, self.config.name)
+
+ def create(self):
+ '''create a project '''
+ cmd = ['new-project', self.config.name]
+ cmd.extend(self.config.to_option_list())
+
+ return self.openshift_cmd(cmd, oadm=True)
+
+ def update(self):
+ '''update a project '''
+
+ self.project.update_annotation('display-name', self.config.config_options['display_name']['value'])
+ self.project.update_annotation('description', self.config.config_options['description']['value'])
+
+ # work around for immutable project field
+ if self.config.config_options['node_selector']['value']:
+ self.project.update_annotation('node-selector', self.config.config_options['node_selector']['value'])
+ else:
+ self.project.update_annotation('node-selector', self.project.find_annotation('node-selector'))
+
+ return self._replace_content(self.kind, self.config.name, self.project.yaml_dict)
+
+ def needs_update(self):
+ ''' verify an update is needed '''
+ result = self.project.find_annotation("display-name")
+ if result != self.config.config_options['display_name']['value']:
+ return True
+
+ result = self.project.find_annotation("description")
+ if result != self.config.config_options['description']['value']:
+ return True
+
+ result = self.project.find_annotation("node-selector")
+ if result != self.config.config_options['node_selector']['value']:
+ return True
+
+ return False
+
+ # pylint: disable=too-many-return-statements,too-many-branches
+ @staticmethod
+ def run_ansible(params, check_mode):
+ '''run the idempotent ansible code'''
+
+ _ns = None
+ if params['node_selector'] is not None:
+ _ns = ','.join(params['node_selector'])
+
+ pconfig = ProjectConfig(params['name'],
+ 'None',
+ params['kubeconfig'],
+ {'admin': {'value': params['admin'], 'include': True},
+ 'admin_role': {'value': params['admin_role'], 'include': True},
+ 'description': {'value': params['description'], 'include': True},
+ 'display_name': {'value': params['display_name'], 'include': True},
+ 'node_selector': {'value': _ns, 'include': True},
+ })
+
+ oadm_project = OCProject(pconfig, verbose=params['debug'])
+
+ state = params['state']
+
+ api_rval = oadm_project.get()
+
+ #####
+ # Get
+ #####
+ if state == 'list':
+ return {'changed': False, 'results': api_rval['results'], 'state': state}
+
+ ########
+ # Delete
+ ########
+ if state == 'absent':
+ if oadm_project.exists():
+
+ if check_mode:
+ return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a delete.'}
+
+ api_rval = oadm_project.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 oadm_project.exists():
+
+ if check_mode:
+ return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'}
+
+ # Create it here
+ api_rval = oadm_project.create()
+
+ # return the created object
+ api_rval = oadm_project.get()
+
+ if api_rval['returncode'] != 0:
+ return {'failed': True, 'msg': api_rval}
+
+ return {'changed': True, 'results': api_rval, 'state': state}
+
+ ########
+ # Update
+ ########
+ if oadm_project.needs_update():
+
+ if check_mode:
+ return {'changed': True, 'msg': 'CHECK_MODE: Would have performed an update.'}
+
+ api_rval = oadm_project.update()
+
+ if api_rval['returncode'] != 0:
+ return {'failed': True, 'msg': api_rval}
+
+ # return the created object
+ api_rval = oadm_project.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/project b/roles/lib_openshift/src/doc/project
new file mode 100644
index 000000000..92efe4320
--- /dev/null
+++ b/roles/lib_openshift/src/doc/project
@@ -0,0 +1,81 @@
+# flake8: noqa
+# pylint: skip-file
+
+DOCUMENTATION = '''
+---
+module: oc_project
+short_description: Module to manage openshift projects
+description:
+ - Manage openshift projects programmatically.
+options:
+ state:
+ description:
+ - If present, the project will be created if it doesn't exist or update if different. If absent, the project will be removed if present. If list, information about the project will be gathered and returned as part of the Ansible call results.
+ 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: []
+ display_name:
+ description:
+ - The display name attribute for a project
+ required: false
+ default: None
+ aliases: []
+ description:
+ description:
+ - The description attribute for a project
+ required: false
+ default: None
+ aliases: []
+ admin:
+ description:
+ - The project admin username
+ required: false
+ default: false
+ aliases: []
+ admin_role:
+ description:
+ - The project admin username
+ required: false
+ default: 'admin'
+ aliases: []
+ node_selector:
+ description:
+ - The node selector for this project.
+ - This allows certain pods in this project to run on certain nodes.
+ required: false
+ default: None
+ aliases: []
+author:
+- "Kenny Woodson <kwoodson@redhat.com>"
+extends_documentation_fragment: []
+'''
+
+EXAMPLES = '''
+- name: create secret
+ oc_project:
+ state: present
+ name: openshift-ops
+ display_name: operations team project
+ node_selector:
+ - top=secret
+ - noncustomer=True
+'''
diff --git a/roles/lib_openshift/src/lib/project.py b/roles/lib_openshift/src/lib/project.py
new file mode 100644
index 000000000..40994741c
--- /dev/null
+++ b/roles/lib_openshift/src/lib/project.py
@@ -0,0 +1,85 @@
+# pylint: skip-file
+# flake8: noqa
+
+
+# pylint: disable=too-many-instance-attributes
+class ProjectConfig(OpenShiftCLIConfig):
+ ''' project config object '''
+ def __init__(self, rname, namespace, kubeconfig, project_options):
+ super(ProjectConfig, self).__init__(rname, None, kubeconfig, project_options)
+
+
+class Project(Yedit):
+ ''' Class to wrap the oc command line tools '''
+ annotations_path = "metadata.annotations"
+ kind = 'Project'
+ annotation_prefix = 'openshift.io/'
+
+ def __init__(self, content):
+ '''Project constructor'''
+ super(Project, self).__init__(content=content)
+
+ def get_annotations(self):
+ ''' return the annotations'''
+ return self.get(Project.annotations_path) or {}
+
+ def add_annotations(self, inc_annos):
+ ''' add an annotation to the other annotations'''
+ if not isinstance(inc_annos, list):
+ inc_annos = [inc_annos]
+
+ annos = self.get_annotations()
+ if not annos:
+ self.put(Project.annotations_path, inc_annos)
+ else:
+ for anno in inc_annos:
+ for key, value in anno.items():
+ annos[key] = value
+
+ return True
+
+ def find_annotation(self, key):
+ ''' find an annotation'''
+ annotations = self.get_annotations()
+ for anno in annotations:
+ if Project.annotation_prefix + key == anno:
+ return annotations[anno]
+
+ return None
+
+ def delete_annotation(self, inc_anno_keys):
+ ''' remove an annotation from a project'''
+ if not isinstance(inc_anno_keys, list):
+ inc_anno_keys = [inc_anno_keys]
+
+ annos = self.get(Project.annotations_path) or {}
+
+ if not annos:
+ return True
+
+ removed = False
+ for inc_anno in inc_anno_keys:
+ anno = self.find_annotation(inc_anno)
+ if anno:
+ del annos[Project.annotation_prefix + anno]
+ removed = True
+
+ return removed
+
+ def update_annotation(self, key, value):
+ ''' remove an annotation for a project'''
+ annos = self.get(Project.annotations_path) or {}
+
+ if not annos:
+ return True
+
+ updated = False
+ anno = self.find_annotation(key)
+ if anno:
+ annos[Project.annotation_prefix + key] = value
+ updated = True
+
+ else:
+ self.add_annotations({Project.annotation_prefix + key: value})
+
+ return updated
diff --git a/roles/lib_openshift/src/sources.yml b/roles/lib_openshift/src/sources.yml
index a0e200d0a..c72fd4ea8 100644
--- a/roles/lib_openshift/src/sources.yml
+++ b/roles/lib_openshift/src/sources.yml
@@ -130,6 +130,17 @@ oc_process.py:
- class/oc_process.py
- ansible/oc_process.py
+oc_project.py:
+- doc/generated
+- doc/license
+- lib/import.py
+- doc/project
+- ../../lib_utils/src/class/yedit.py
+- lib/base.py
+- lib/project.py
+- class/oc_project.py
+- ansible/oc_project.py
+
oc_route.py:
- doc/generated
- doc/license
diff --git a/roles/lib_openshift/src/test/integration/oc_project.yml b/roles/lib_openshift/src/test/integration/oc_project.yml
new file mode 100755
index 000000000..9f700c62c
--- /dev/null
+++ b/roles/lib_openshift/src/test/integration/oc_project.yml
@@ -0,0 +1,83 @@
+#!/usr/bin/ansible-playbook --module-path=../../../library/
+# ./oc_project.yml -M ../../../library -e "cli_master_test=$OPENSHIFT_MASTER
+---
+- hosts: "{{ cli_master_test }}"
+ gather_facts: no
+ user: root
+ tasks:
+ - name: create a project
+ oc_project:
+ display_name: operations project
+ name: operations
+ state: present
+ description: All things operations
+ node_selector:
+ - ops_only=true
+ register: projout
+ - debug: var=projout
+
+ - assert:
+ that:
+ - "projout.results.results['metadata']['name'] == 'operations'"
+ - projout.changed
+ msg: project create failed.
+
+ - name: create a project
+ oc_project:
+ display_name: operations project
+ name: operations
+ state: present
+ description: All things operations
+ node_selector:
+ - ops_only=true
+ register: projout
+ - debug: var=projout
+
+ - assert:
+ that:
+ - "projout.results.results['metadata']['name'] == 'operations'"
+ - projout.changed == False
+ msg: project create failed.
+
+ - name: update a project
+ oc_project:
+ display_name: operations project one
+ name: operations
+ state: present
+ description: All things operations
+ node_selector:
+ - ops_only=true
+ register: projout
+ - debug: var=projout
+
+ - assert:
+ that:
+ - "projout.results.results['metadata']['annotations']['openshift.io/display-name'] == 'operations project one'"
+ - projout.changed == True
+ msg: project create failed.
+
+ - name: update a project
+ oc_project:
+ name: operations
+ state: list
+ register: projout
+ - debug: var=projout
+
+ - assert:
+ that:
+ - "projout.results['metadata']['annotations']['openshift.io/display-name'] == 'operations project one'"
+ - projout.changed == False
+ - projout.state == 'list'
+ msg: project list failed.
+
+ - name: delete a project
+ oc_project:
+ name: operations
+ state: absent
+ register: projout
+ - debug: var=projout
+
+ - assert:
+ that:
+ - projout.changed == True
+ msg: project delete failed.
diff --git a/roles/lib_openshift/src/test/unit/test_oc_project.py b/roles/lib_openshift/src/test/unit/test_oc_project.py
new file mode 100755
index 000000000..50d81e6a6
--- /dev/null
+++ b/roles/lib_openshift/src/test/unit/test_oc_project.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python2
+'''
+ Unit tests for oc project
+'''
+# To run:
+# ./oc_secret.py
+#
+# .
+# Ran 1 test in 0.002s
+#
+# 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,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_project import OCProject # noqa: E402
+
+
+class OCProjectTest(unittest.TestCase):
+ '''
+ Test class for OCSecret
+ '''
+
+ def setUp(self):
+ ''' setup method will create a file and set to known configuration '''
+ pass
+
+ @mock.patch('oc_project.locate_oc_binary')
+ @mock.patch('oc_project.Utils.create_tmpfile_copy')
+ @mock.patch('oc_project.Utils._write')
+ @mock.patch('oc_project.OCProject._run')
+ def test_adding_a_project(self, mock_cmd, mock_write, mock_tmpfile_copy, mock_loc_oc_bin):
+ ''' Testing adding a project '''
+
+ # Arrange
+
+ # run_ansible input parameters
+ params = {
+ 'state': 'present',
+ 'display_name': 'operations project',
+ 'name': 'operations',
+ 'node_selector': ['ops_only=True'],
+ 'kubeconfig': '/etc/origin/master/admin.kubeconfig',
+ 'debug': False,
+ 'admin': None,
+ 'admin_role': 'admin',
+ 'description': 'All things operations project',
+ }
+
+ project_results = '''{
+ "kind": "Project",
+ "apiVersion": "v1",
+ "metadata": {
+ "name": "operations",
+ "selfLink": "/oapi/v1/projects/operations",
+ "uid": "5e52afb8-ee33-11e6-89f4-0edc441d9666",
+ "resourceVersion": "1584",
+ "labels": {},
+ "annotations": {
+ "openshift.io/node-selector": "ops_only=True",
+ "openshift.io/sa.initialized-roles": "true",
+ "openshift.io/sa.scc.mcs": "s0:c3,c2",
+ "openshift.io/sa.scc.supplemental-groups": "1000010000/10000",
+ "openshift.io/sa.scc.uid-range": "1000010000/10000"
+ }
+ },
+ "spec": {
+ "finalizers": [
+ "kubernetes",
+ "openshift.io/origin"
+ ]
+ },
+ "status": {
+ "phase": "Active"
+ }
+ }'''
+
+ # Return values of our mocked function call. These get returned once per call.
+ mock_cmd.side_effect = [
+ (1, '', 'Error from server: namespaces "operations" not found'),
+ (1, '', 'Error from server: namespaces "operations" not found'),
+ (0, '', ''), # created
+ (0, project_results, ''), # fetch it
+ ]
+
+ mock_tmpfile_copy.side_effect = [
+ '/tmp/mocked_kubeconfig',
+ ]
+
+ mock_loc_oc_bin.side_effect = [
+ 'oc',
+ ]
+
+ # Act
+
+ results = OCProject.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', 'namespace', 'operations', '-o', 'json'], None),
+ mock.call(['oc', 'get', 'namespace', 'operations', '-o', 'json'], None),
+ mock.call(['oc', 'adm', 'new-project', 'operations', mock.ANY,
+ mock.ANY, mock.ANY, mock.ANY], None),
+ mock.call(['oc', 'get', 'namespace', 'operations', '-o', 'json'], None),
+
+ ])
+
+ def tearDown(self):
+ '''TearDown method'''
+ pass
+
+
+if __name__ == "__main__":
+ unittest.main()