diff options
Diffstat (limited to 'roles/lib_openshift/src')
-rw-r--r-- | roles/lib_openshift/src/ansible/oc_service.py | 36 | ||||
-rw-r--r-- | roles/lib_openshift/src/class/oc_service.py | 168 | ||||
-rw-r--r-- | roles/lib_openshift/src/doc/service | 121 | ||||
-rw-r--r-- | roles/lib_openshift/src/lib/service.py | 124 | ||||
-rw-r--r-- | roles/lib_openshift/src/sources.yml | 41 | ||||
-rwxr-xr-x | roles/lib_openshift/src/test/integration/oc_service.yml | 91 |
6 files changed, 566 insertions, 15 deletions
diff --git a/roles/lib_openshift/src/ansible/oc_service.py b/roles/lib_openshift/src/ansible/oc_service.py new file mode 100644 index 000000000..9eb144e9c --- /dev/null +++ b/roles/lib_openshift/src/ansible/oc_service.py @@ -0,0 +1,36 @@ +# pylint: skip-file +# flake8: noqa + +def main(): + ''' + ansible oc module for services + ''' + + 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'), + labels=dict(default=None, type='dict'), + selector=dict(default=None, type='dict'), + clusterip=dict(default=None, type='str'), + portalip=dict(default=None, type='str'), + ports=dict(default=None, type='list'), + session_affinity=dict(default='None', type='str'), + service_type=dict(default='ClusterIP', type='str'), + ), + supports_check_mode=True, + ) + + rval = OCService.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_service.py b/roles/lib_openshift/src/class/oc_service.py new file mode 100644 index 000000000..6871e00d1 --- /dev/null +++ b/roles/lib_openshift/src/class/oc_service.py @@ -0,0 +1,168 @@ +# pylint: skip-file +# flake8: noqa + + +# pylint: disable=too-many-instance-attributes +class OCService(OpenShiftCLI): + ''' Class to wrap the oc command line tools ''' + kind = 'service' + + # pylint allows 5 + # pylint: disable=too-many-arguments + def __init__(self, + sname, + namespace, + labels, + selector, + cluster_ip, + portal_ip, + ports, + session_affinity, + service_type, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False): + ''' Constructor for OCVolume ''' + super(OCService, self).__init__(namespace, kubeconfig) + self.namespace = namespace + self.config = ServiceConfig(sname, namespace, ports, selector, labels, + cluster_ip, portal_ip, session_affinity, service_type) + self.user_svc = Service(content=self.config.data) + self.svc = None + + @property + def service(self): + ''' property function service''' + if not self.svc: + self.get() + return self.svc + + @service.setter + def service(self, data): + ''' setter function for yedit var ''' + self.svc = data + + def exists(self): + ''' return whether a volume exists ''' + if self.service: + return True + + return False + + def get(self): + '''return volume information ''' + result = self._get(self.kind, self.config.name) + if result['returncode'] == 0: + self.service = Service(content=result['results'][0]) + result['clusterip'] = self.service.get('spec.clusterIP') + elif 'services \"%s\" not found' % self.config.name in result['stderr']: + result['clusterip'] = '' + + return result + + def delete(self): + '''delete the object''' + return self._delete(self.kind, self.config.name) + + def create(self): + '''create a service ''' + return self._create_from_content(self.config.name, self.user_svc.yaml_dict) + + def update(self): + '''create a service ''' + # Need to copy over the portalIP and the serviceIP settings + + self.user_svc.add_cluster_ip(self.service.get('spec.clusterIP')) + self.user_svc.add_portal_ip(self.service.get('spec.portalIP')) + return self._replace_content(self.kind, self.config.name, self.user_svc.yaml_dict) + + def needs_update(self): + ''' verify an update is needed ''' + skip = ['clusterIP', 'portalIP'] + return not Utils.check_def_equal(self.user_svc.yaml_dict, self.service.yaml_dict, skip_keys=skip, debug=True) + + @staticmethod + def run_ansible(params, check_mode): + '''Run the idempotent ansible code''' + oc_svc = OCService(params['name'], + params['namespace'], + params['labels'], + params['selector'], + params['clusterip'], + params['portalip'], + params['ports'], + params['session_affinity'], + params['service_type']) + + state = params['state'] + + api_rval = oc_svc.get() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + ##### + # Get + ##### + if state == 'list': + return {'changed': False, 'results': api_rval, 'state': state} + + ######## + # Delete + ######## + if state == 'absent': + if oc_svc.exists(): + + if check_mode: + return {'changed': True, + 'msg': 'CHECK_MODE: Would have performed a delete.'} # noqa: E501 + + api_rval = oc_svc.delete() + + return {'changed': True, 'results': api_rval, 'state': state} + + return {'changed': False, 'state': state} + + if state == 'present': + ######## + # Create + ######## + if not oc_svc.exists(): + + if check_mode: + return {'changed': True, + 'msg': 'CHECK_MODE: Would have performed a create.'} # noqa: E501 + + # Create it here + api_rval = oc_svc.create() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + # return the created object + api_rval = oc_svc.get() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + return {'changed': True, 'results': api_rval, 'state': state} + + ######## + # Update + ######## + if oc_svc.needs_update(): + api_rval = oc_svc.update() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + # return the created object + api_rval = oc_svc.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. [%s]' % state} diff --git a/roles/lib_openshift/src/doc/service b/roles/lib_openshift/src/doc/service new file mode 100644 index 000000000..adc2ad949 --- /dev/null +++ b/roles/lib_openshift/src/doc/service @@ -0,0 +1,121 @@ +# flake8: noqa +# pylint: skip-file + +DOCUMENTATION = ''' +--- +module: oc_service +short_description: Create, modify, and idempotently manage openshift services. +description: + - Manage openshift service objects programmatically. +options: + state: + description: + - State represents whether to create, modify, delete, or list + required: true + 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: default + aliases: [] + selector: + description: + - The selector to apply when filtering for services. + required: false + default: None + aliases: [] + labels: + description: + - The labels to apply on the service. + required: false + default: None + aliases: [] + clusterip: + description: + - The cluster ip address to use with this service. + required: false + default: None + aliases: [] + portalip: + description: + - The portal ip address to use with this service. + required: false + default: None + aliases: [] + ports: + description: + - A list of the ports that are used for this service. This includes name, port, protocol, and targetPort. + - See examples. + required: false + default: None + aliases: [] + session_affinity: + description: + - The type of session affinity to use. + required: false + default: 'None' + aliases: [] + service_type: + description: + - The type of service desired. Each option tells the service to behave accordingly. + - https://kubernetes.io/docs/user-guide/services/ + required: false + default: ClusterIP + choices: + - ClusterIP + - NodePort + - LoadBalancer + - ExternalName + aliases: [] +author: +- "Kenny Woodson <kwoodson@redhat.com>" +extends_documentation_fragment: [] +''' + +EXAMPLES = ''' +- name: get docker-registry service + run_once: true + oc_service: + namespace: default + name: docker-registry + state: list + register: registry_service_out + +- name: create the docker-registry service + oc_service: + namespace: default + name: docker-registry + ports: + - name: 5000-tcp + port: 5000 + protocol: TCP + targetPort: 5000 + selector: + docker-registry: default + session_affinity: ClientIP + service_type: ClusterIP + register: svc_out + notify: + - restart openshift master services +''' diff --git a/roles/lib_openshift/src/lib/service.py b/roles/lib_openshift/src/lib/service.py new file mode 100644 index 000000000..4f69fca9b --- /dev/null +++ b/roles/lib_openshift/src/lib/service.py @@ -0,0 +1,124 @@ +# pylint: skip-file + +# pylint: disable=too-many-instance-attributes +class ServiceConfig(object): + ''' Handle service options ''' + # pylint: disable=too-many-arguments + def __init__(self, + sname, + namespace, + ports, + selector=None, + labels=None, + cluster_ip=None, + portal_ip=None, + session_affinity=None, + service_type=None): + ''' constructor for handling service options ''' + self.name = sname + self.namespace = namespace + self.ports = ports + self.selector = selector + self.labels = labels + self.cluster_ip = cluster_ip + self.portal_ip = portal_ip + self.session_affinity = session_affinity + self.service_type = service_type + self.data = {} + + self.create_dict() + + def create_dict(self): + ''' return a service as a dict ''' + self.data['apiVersion'] = 'v1' + self.data['kind'] = 'Service' + self.data['metadata'] = {} + self.data['metadata']['name'] = self.name + self.data['metadata']['namespace'] = self.namespace + if self.labels: + for lab, lab_value in self.labels.items(): + self.data['metadata'][lab] = lab_value + self.data['spec'] = {} + + if self.ports: + self.data['spec']['ports'] = self.ports + else: + self.data['spec']['ports'] = [] + + if self.selector: + self.data['spec']['selector'] = self.selector + + self.data['spec']['sessionAffinity'] = self.session_affinity or 'None' + + if self.cluster_ip: + self.data['spec']['clusterIP'] = self.cluster_ip + + if self.portal_ip: + self.data['spec']['portalIP'] = self.portal_ip + + if self.service_type: + self.data['spec']['type'] = self.service_type + +# pylint: disable=too-many-instance-attributes,too-many-public-methods +class Service(Yedit): + ''' Class to wrap the oc command line tools ''' + port_path = "spec.ports" + portal_ip = "spec.portalIP" + cluster_ip = "spec.clusterIP" + kind = 'Service' + + def __init__(self, content): + '''Service constructor''' + super(Service, self).__init__(content=content) + + def get_ports(self): + ''' get a list of ports ''' + return self.get(Service.port_path) or [] + + def add_ports(self, inc_ports): + ''' add a port object to the ports list ''' + if not isinstance(inc_ports, list): + inc_ports = [inc_ports] + + ports = self.get_ports() + if not ports: + self.put(Service.port_path, inc_ports) + else: + ports.extend(inc_ports) + + return True + + def find_ports(self, inc_port): + ''' find a specific port ''' + for port in self.get_ports(): + if port['port'] == inc_port['port']: + return port + + return None + + def delete_ports(self, inc_ports): + ''' remove a port from a service ''' + if not isinstance(inc_ports, list): + inc_ports = [inc_ports] + + ports = self.get(Service.port_path) or [] + + if not ports: + return True + + removed = False + for inc_port in inc_ports: + port = self.find_ports(inc_port) + if port: + ports.remove(port) + removed = True + + return removed + + def add_cluster_ip(self, sip): + '''add cluster ip''' + self.put(Service.cluster_ip, sip) + + def add_portal_ip(self, pip): + '''add cluster ip''' + self.put(Service.portal_ip, pip) diff --git a/roles/lib_openshift/src/sources.yml b/roles/lib_openshift/src/sources.yml index 28929c02a..b363e0798 100644 --- a/roles/lib_openshift/src/sources.yml +++ b/roles/lib_openshift/src/sources.yml @@ -40,17 +40,6 @@ oc_route.py: - class/oc_route.py - ansible/oc_route.py -oc_secret.py: -- doc/generated -- doc/license -- lib/import.py -- doc/secret -- ../../lib_utils/src/class/yedit.py -- lib/base.py -- lib/secret.py -- class/oc_secret.py -- ansible/oc_secret.py - oc_scale.py: - doc/generated - doc/license @@ -63,15 +52,27 @@ oc_scale.py: - class/oc_scale.py - ansible/oc_scale.py -oc_version.py: +oc_secret.py: - doc/generated - doc/license - lib/import.py -- doc/version +- doc/secret - ../../lib_utils/src/class/yedit.py - lib/base.py -- class/oc_version.py -- ansible/oc_version.py +- lib/secret.py +- class/oc_secret.py +- ansible/oc_secret.py + +oc_service.py: +- doc/generated +- doc/license +- lib/import.py +- doc/service +- ../../lib_utils/src/class/yedit.py +- lib/base.py +- lib/service.py +- class/oc_service.py +- ansible/oc_service.py oc_serviceaccount.py: - doc/generated @@ -83,3 +84,13 @@ oc_serviceaccount.py: - lib/serviceaccount.py - class/oc_serviceaccount.py - ansible/oc_serviceaccount.py + +oc_version.py: +- doc/generated +- doc/license +- lib/import.py +- doc/version +- ../../lib_utils/src/class/yedit.py +- lib/base.py +- class/oc_version.py +- ansible/oc_version.py diff --git a/roles/lib_openshift/src/test/integration/oc_service.yml b/roles/lib_openshift/src/test/integration/oc_service.yml new file mode 100755 index 000000000..4222d32b2 --- /dev/null +++ b/roles/lib_openshift/src/test/integration/oc_service.yml @@ -0,0 +1,91 @@ +#!/usr/bin/ansible-playbook --module-path=../../../library/ +# ./oc_service.yml -M ../../../library -e "cli_master_test=$OPENSHIFT_MASTER +--- +- hosts: "{{ cli_master_test }}" + gather_facts: no + user: root + tasks: + - name: create the default registry service + oc_service: + namespace: default + name: test-registry + ports: + - name: 9000-tcp + port: 9000 + protocol: TCP + targetPort: 9000 + selector: + test-registtry: default + session_affinity: ClientIP + service_type: ClusterIP + register: svcout + + - assert: + that: "svcout.results.results[0]['metadata']['name'] == 'test-registry'" + msg: route create failed + + - name: oc_service + oc_service: + name: test-registry + namespace: default + state: list + register: svc_out + + - assert: + that: "svcout.results.results[0]['metadata']['name'] == 'test-registry'" + msg: route create failed + + +# - name: get route +# oc_route: +# state: list +# name: test +# namespace: default +# register: routeout +# - debug: var=routeout +# +# - assert: +# that: "routeout.results[0]['metadata']['name'] == 'test'" +# msg: get route failed +# +# - name: delete route +# oc_route: +# state: absent +# name: test +# namespace: default +# register: routeout +# - debug: var=routeout +# +# - assert: +# that: "routeout.results.returncode == 0" +# msg: delete route failed +# +# - name: create route +# oc_route: +# name: test +# namespace: default +# tls_termination: edge +# cert_content: testing cert +# cacert_content: testing cacert +# key_content: testing key +# service_name: test +# host: test.example +# register: routeout +# - debug: var=routeout +# +# - name: create route noop +# oc_route: +# name: test +# namespace: default +# tls_termination: edge +# cert_content: testing cert +# cacert_content: testing cacert +# key_content: testing key +# service_name: test +# host: test.example +# register: routeout +# - debug: var=routeout +# +# - assert: +# that: "routeout.changed == False" +# msg: Route create not idempotent |