From e73761cdee47de9d91324e45c42eeb8ac8ecb819 Mon Sep 17 00:00:00 2001 From: "Kenneth D. Evensen" Date: Tue, 10 Oct 2017 08:50:43 -0400 Subject: [PATCH] Lookup plugin for the OpenShift Container Platform. --- lib/ansible/plugins/lookup/openshift.py | 248 ++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 lib/ansible/plugins/lookup/openshift.py diff --git a/lib/ansible/plugins/lookup/openshift.py b/lib/ansible/plugins/lookup/openshift.py new file mode 100644 index 00000000000000..6753141971bef0 --- /dev/null +++ b/lib/ansible/plugins/lookup/openshift.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +# (c) 2017, Kenneth D. Evensen + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = """ + lookup: openshift + version_added: "2.5" + short_description: Returns the JSON definition of an object in OpenShift + description: + - This lookup plugin provides the ability to query an OpenShift Container + - platform cluster for information about objects. This plugin requires + - a valid user or service account token. + options: + kind: + description: + - The kind of OpenShift resource to read (e.g. Project, Service, Pod) + required: True + host: + description: + - The IP address of the host serving the OpenShift API + required: False + default: 127.0.0.1 + port: + description: + - The port on which to access the OpenShift API + required: False + default: 8443 + token: + description: + - The token to use for authentication against the OpenShift API. + - This can be a user or ServiceAccount token. + required: True + validate_certs: + description: + - Whether or not to validate the TLS certificate of the API. + required: False + default: True + namespace: + description: + - The namespace/project where the object resides. + required: False + resource_name: + description: + - The name of the object to query. + required: False + pretty: + description: + - Whether or not to prettify the output. This is useful for debugging. + required: False + default: False + labelSelector: + description: + - Additional labels to include in the query. + required: False + fieldSelector: + description: + - Specific fields on which to query. + required: False + resourceVersion: + description: + - Query for a specific resource version. + required: False +""" + +EXAMPLES = """ +- name: Get Project {{ project_name }} + set_fact: + project_fact: "{{ lookup('openshift', + kind='Project', + host=inventory_host, + token=hostvars[inventory_host]['ansible_sa_token'], + resource_name=project_name, + validate_certs=validate_certs) }}" +- name: Get All Service Accounts in a Project + set_fact: + service_fact: "{{ lookup('openshift', + kind='ServiceAccount', + host=inventory_host, + token=hostvars[inventory_host]['ansible_sa_token'], + namespace=project_name, + validate_certs=validate_certs) }}" +""" + +RETURN = """ + _list: + description: + - An object definition or list of objects definitions returned from OpenShift. + type: dict +""" + +import json +from ansible.errors import AnsibleError +from ansible.plugins.lookup import LookupBase +from ansible.module_utils import urls +from ansible.module_utils.six.moves import urllib +from ansible.module_utils.six.moves import urllib_error +from ansible.module_utils.six.moves.urllib.parse import urlencode +from ansible.module_utils._text import to_text +from ansible.module_utils._text import to_native + + +class OcpQuery(object): + def __init__(self, host, port, token, validate_certs): + self.apis = ['api', 'oapi'] + self.token = token + self.validate_certs = validate_certs + self.host = host + self.port = port + self.kinds = {} + bearer = "Bearer " + self.token + self.headers = {"Authorization": bearer} + self.build_facts() + + def build_facts(self): + + for api in self.apis: + url = "https://{0}:{1}/{2}/v1".format(self.host, self.port, api) + try: + response = urls.open_url(url=url, + headers=self.headers, + validate_certs=self.validate_certs, + method='get') + except urllib_error.HTTPError as error: + try: + body = to_native(error.read()) + except AttributeError: + body = '' + raise AnsibleError("OC Query raised exception with code {0} and message {1} against url {2}".format(error.code, body, url)) + + for resource in json.loads(to_text(response.read(), errors='surrogate_or_strict'))['resources']: + if 'generated' not in resource['name']: + self.kinds[resource['kind']] = \ + {'kind': resource['kind'], + 'name': resource['name'].split('/')[0], + 'namespaced': resource['namespaced'], + 'api': api, + 'version': 'v1', + 'baseurl': url + } + + def url(self, kind=None, namespace=None, resource_name=None, pretty=False, labelSelector=None, fieldSelector=None, resourceVersion=None): + first_param = True + + url = [self.kinds[kind]['baseurl']] + if self.kinds[kind]['namespaced'] is True: + url.append('/namespaces/') + if namespace is None: + raise AnsibleError('Kind %s requires a namespace.' + ' None provided' % kind) + url.append(namespace) + + url.append('/' + self.kinds[kind]['name']) + + if resource_name is not None: + url.append('/' + resource_name) + + if pretty: + url.append('?pretty') + first_param = False + + if labelSelector is not None: + if first_param: + url.append('?') + else: + url.append('&') + + url.append(urlencode({'labelSelector': labelSelector})) + first_param = False + + if fieldSelector is not None: + if first_param: + url.append('?') + else: + url.append('&') + + url.append(urlencode({'fieldSelector': fieldSelector})) + first_param = False + + if resourceVersion is not None: + if first_param: + url.append('?') + else: + url.append('&') + + url.append(urlencode({'resourceVersion': resourceVersion})) + first_param = False + + return "".join(url) + + def query(self, kind=None, namespace=None, resource_name=None, pretty=False, labelSelector=None, fieldSelector=None, resourceVersion=None): + url = self.url(kind=kind, + namespace=namespace, + resource_name=resource_name, + pretty=pretty, + labelSelector=labelSelector, + fieldSelector=fieldSelector, + resourceVersion=resourceVersion) + + try: + response = urls.open_url(url=url, + headers=self.headers, + validate_certs=self.validate_certs, + method='get') + except urllib_error.HTTPError as error: + try: + body = to_native(error.read()) + except AttributeError: + body = '' + raise AnsibleError("OC Query raised exception with code {0} and message {1} against url {2}".format(error.code, body, url)) + + return json.loads(to_text(response.read(), errors='surrogate_or_strict')) + + +class LookupModule(LookupBase): + def run(self, terms, variables=None, **kwargs): + + host = kwargs.get('host', '127.0.0.1') + port = kwargs.get('port', '8443') + validate_certs = kwargs.get('validate_certs', True) + token = kwargs.get('token', None) + + namespace = kwargs.get('namespace', None) + resource_name = kwargs.get('resource_name', None) + pretty = kwargs.get('pretty', False) + label_selector = kwargs.get('labelSelector', None) + field_selector = kwargs.get('fieldSelector', None) + resource_version = kwargs.get('resourceVersion', None) + resource_kind = kwargs.get('kind', None) + + ocp = OcpQuery(host, port, token, validate_certs) + + search_response = ocp.query(kind=resource_kind, + namespace=namespace, + resource_name=resource_name, + pretty=pretty, + labelSelector=label_selector, + fieldSelector=field_selector, + resourceVersion=resource_version) + if search_response is not None and "items" in search_response: + search_response['item_list'] = search_response.pop('items') + + values = [search_response] + + return values