From 85ff38d2e2a896b7fa20f0d34fbf31e755d2e0c9 Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Thu, 4 Jan 2018 15:24:31 -0500 Subject: [PATCH] K8s lookup plugin (#34025) * Use OpenShift client * Refactored * Fix lint issues * Replace AnsibleError to make the bot happy * Remove extra space --- lib/ansible/module_utils/k8s/common.py | 37 ++- lib/ansible/module_utils/k8s/lookup.py | 219 ++++++++++++++ lib/ansible/plugins/lookup/k8s.py | 198 +++++++++++++ lib/ansible/plugins/lookup/openshift.py | 367 ++++++++++-------------- 4 files changed, 591 insertions(+), 230 deletions(-) create mode 100644 lib/ansible/module_utils/k8s/lookup.py create mode 100644 lib/ansible/plugins/lookup/k8s.py diff --git a/lib/ansible/module_utils/k8s/common.py b/lib/ansible/module_utils/k8s/common.py index 45f9fc947a0..369b221de32 100644 --- a/lib/ansible/module_utils/k8s/common.py +++ b/lib/ansible/module_utils/k8s/common.py @@ -70,6 +70,20 @@ def remove_secret_data(obj_dict): obj_dict['metadata']['annotations'].pop(key) +def to_snake(name): + """ Convert a string from camel to snake """ + if not name: + return name + + def _replace(m): + m = m.group(0) + return m[0] + '_' + m[1:] + + p = r'[a-z][A-Z]|' \ + r'[A-Z]{2}[a-z]' + return re.sub(p, _replace, name).lower() + + class DateTimeEncoder(json.JSONEncoder): # When using json.dumps() with K8s object, pass cls=DateTimeEncoder to handle any datetime objects def default(self, o): @@ -122,7 +136,7 @@ class KubernetesAnsibleModule(AnsibleModule): self.kind = self.resource_definition.get('kind') self.api_version = self.api_version.lower() - self.kind = self._to_snake(self.kind) + self.kind = to_snake(self.kind) if not self.api_version: self.fail_json( @@ -320,7 +334,7 @@ class KubernetesAnsibleModule(AnsibleModule): def _add_parameter(self, request, path, parameters): for key, value in iteritems(request): if path: - param_name = '_'.join(path + [self._to_snake(key)]) + param_name = '_'.join(path + [to_snake(key)]) else: param_name = self.helper.attribute_to_snake(key) if param_name in self.helper.argspec and value is not None: @@ -335,25 +349,6 @@ class KubernetesAnsibleModule(AnsibleModule): "expected by the OpenShift Python module.".format(param_name)) ) - @staticmethod - def _to_snake(name): - """ - Convert a string from camel to snake - :param name: string to convert - :return: string - """ - if not name: - return name - - def replace(m): - m = m.group(0) - return m[0] + '_' + m[1:] - - p = r'[a-z][A-Z]|' \ - r'[A-Z]{2}[a-z]' - result = re.sub(p, replace, name) - return result.lower() - class OpenShiftAnsibleModuleHelper(AnsibleMixin, OpenShiftObjectHelper): pass diff --git a/lib/ansible/module_utils/k8s/lookup.py b/lib/ansible/module_utils/k8s/lookup.py new file mode 100644 index 00000000000..6058f593097 --- /dev/null +++ b/lib/ansible/module_utils/k8s/lookup.py @@ -0,0 +1,219 @@ +# +# Copyright 2018 Red Hat | Ansible +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +from __future__ import absolute_import, division, print_function + +import json +import os + +from ansible.module_utils.k8s.common import DateTimeEncoder, remove_secret_data, to_snake +from ansible.module_utils.k8s.helper import AUTH_ARG_SPEC + +try: + from openshift.helper.kubernetes import KubernetesObjectHelper + from openshift.helper.openshift import OpenShiftObjectHelper + from openshift.helper.exceptions import KubernetesException + HAS_K8S_MODULE_HELPER = True +except ImportError as exc: + HAS_K8S_MODULE_HELPER = False + +try: + import yaml + HAS_YAML = True +except ImportError: + HAS_YAML = False + + +class KubernetesLookup(object): + + def __init__(self): + + if not HAS_K8S_MODULE_HELPER: + raise Exception( + "Requires the OpenShift Python client. Try `pip install openshift`" + ) + + if not HAS_YAML: + raise Exception( + "Requires PyYAML. Try `pip install PyYAML`" + ) + + self.kind = None + self.name = None + self.namespace = None + self.api_version = None + self.label_selector = None + self.field_selector = None + self.include_uninitialized = None + self.resource_definition = None + self.helper = None + self.connection = {} + + def run(self, terms, variables=None, **kwargs): + self.mylog('Here!') + self.kind = kwargs.get('kind') + self.name = kwargs.get('resource_name') + self.namespace = kwargs.get('namespace') + self.api_version = kwargs.get('api_version', 'v1') + self.label_selector = kwargs.get('label_selector') + self.field_selector = kwargs.get('field_selector') + self.include_uninitialized = kwargs.get('include_uninitialized', False) + + resource_definition = kwargs.get('resource_definition') + src = kwargs.get('src') + if src: + resource_definition = self.load_resource_definition(src) + if resource_definition: + self.params_from_resource_definition(resource_definition) + + if not self.kind: + raise Exception( + "Error: no Kind specified. Use the 'kind' parameter, or provide an object YAML configuration " + "using the 'resource_definition' parameter." + ) + + self.kind = to_snake(self.kind) + self.helper = self.get_helper() + + for arg in AUTH_ARG_SPEC: + self.connection[arg] = kwargs.get(arg) + + try: + self.helper.set_client_config(**self.connection) + except Exception as exc: + raise Exception( + "Client authentication failed: {0}".format(exc.message) + ) + + if self.name: + self.mylog("Calling get_object()") + return self.get_object() + + return self.list_objects() + + def mylog(self, msg): + with open('loggit.txt', 'a') as f: + f.write(msg + '\n') + + def get_helper(self): + try: + helper = KubernetesObjectHelper(api_version=self.api_version, kind=self.kind, debug=False) + helper.get_model(self.api_version, self.kind) + return helper + except KubernetesException as exc: + raise Exception("Error initializing helper: {0}".format(exc.message)) + + def load_resource_definition(self, src): + """ Load the requested src path """ + path = os.path.normpath(src) + if not os.path.exists(path): + raise Exception("Error accessing {0}. Does the file exist?".format(path)) + try: + result = yaml.safe_load(open(path, 'r')) + except (IOError, yaml.YAMLError) as exc: + raise Exception("Error loading resource_definition: {0}".format(exc)) + return result + + def params_from_resource_definition(self, defn): + if defn.get('apiVersion'): + self.api_version = defn['apiVersion'] + if defn.get('kind'): + self.kind = defn['kind'] + if defn.get('metadata', {}).get('name'): + self.name = defn['metadata']['name'] + if defn.get('metadata', {}).get('namespace'): + self.namespace = defn['metadata']['namespace'] + + def get_object(self): + """ Fetch a named object """ + try: + result = self.helper.get_object(self.name, self.namespace) + except KubernetesException as exc: + raise Exception('Failed to retrieve requested object: {0}'.format(exc.message)) + self.mylog("Got restult") + response = [] + if result is not None: + # Convert Datetime objects to ISO format + result_json = json.loads(json.dumps(result.to_dict(), cls=DateTimeEncoder)) + if self.kind == 'secret': + remove_secret_data(result_json) + response.append(result_json) + + return response + + def list_objects(self): + """ Query for a set of objects """ + if self.namespace: + method_name = 'list_namespaced_{0}'.format(self.kind) + try: + method = self.helper.lookup_method(method_name=method_name) + except KubernetesException: + raise Exception( + "Failed to find method {0} for API {1}".format(method_name, self.api_version) + ) + else: + method_name = 'list_{0}_for_all_namespaces'.format(self.kind) + try: + method = self.helper.lookup_method(method_name=method_name) + except KubernetesException: + method_name = 'list_{0}'.format(self.kind) + try: + method = self.helper.lookup_method(method_name=method_name) + except KubernetesException: + raise Exception( + "Failed to find method for API {0} and Kind {1}".format(self.api_version, self.kind) + ) + + params = {} + if self.field_selector: + params['field_selector'] = self.field_selector + if self.label_selector: + params['label_selector'] = self.label_selector + params['include_uninitialized'] = self.include_uninitialized + + if self.namespace: + try: + result = method(self.namespace, **params) + except KubernetesException as exc: + raise Exception(exc.message) + else: + try: + result = method(**params) + except KubernetesException as exc: + raise Exception(exc.message) + + response = [] + if result is not None: + # Convert Datetime objects to ISO format + result_json = json.loads(json.dumps(result.to_dict(), cls=DateTimeEncoder)) + response = result_json.get('items', []) + if self.kind == 'secret': + for item in response: + remove_secret_data(item) + return response + + +class OpenShiftLookup(KubernetesLookup): + + def get_helper(self): + try: + helper = OpenShiftObjectHelper(api_version=self.api_version, kind=self.kind, debug=False) + helper.get_model(self.api_version, self.kind) + return helper + except KubernetesException as exc: + raise Exception("Error initializing helper: {0}".format(exc.message)) diff --git a/lib/ansible/plugins/lookup/k8s.py b/lib/ansible/plugins/lookup/k8s.py new file mode 100644 index 00000000000..d567332347c --- /dev/null +++ b/lib/ansible/plugins/lookup/k8s.py @@ -0,0 +1,198 @@ +# +# Copyright 2018 Red Hat | Ansible +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +DOCUMENTATION = """ + lookup: k8s + + version_added: "2.5" + + short_description: Query the K8s API + + description: + - Uses the OpenShift Python client to fetch a specific object by name, all matching objects within a + namespace, or all matching objects for all namespaces. + - Provides access the full range of K8s APIs. + - Enables authentication via config file, certificates, password or token. + + options: + api_version: + description: + - Use to specify the API version. If I(resource definition) is provided, the I(apiVersion) from the + I(resource_definition) will override this option. + default: v1 + kind: + description: + - Use to specify an object model. If I(resource definition) is provided, the I(kind) from a + I(resource_definition) will override this option. + required: true + name: + description: + - Fetch a specific object by name. If I(resource definition) is provided, the I(metadata.name) value + from the I(resource_definition) will override this option. + namespace: + description: + - Limit the objects returned to a specific namespace. If I(resource definition) is provided, the + I(metadata.namespace) value from the I(resource_definition) will override this option. + label_selector: + description: + - Additional labels to include in the query. Ignored when I(resource_name) is provided. + field_selector: + description: + - Specific fields on which to query. Ignored when I(resource_name) is provided. + resource_definition: + description: + - "Provide a YAML configuration for an object. NOTE: I(kind), I(api_version), I(resource_name), + and I(namespace) will be overwritten by corresponding values found in the provided I(resource_definition)." + src: + description: + - "Provide a path to a file containing a valid YAML definition of an object dated. Mutually + exclusive with I(resource_definition). NOTE: I(kind), I(api_version), I(resource_name), and I(namespace) + will be overwritten by corresponding values found in the configuration read in from the I(src) file." + - Reads from the local file system. To read from the Ansible controller's file system, use the file lookup + plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to + I(resource_definition). See Examples below. + host: + description: + - Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable. + api_key: + description: + - Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable. + kubeconfig: + description: + - Path to an existing Kubernetes config file. If not provided, and no other connection + options are provided, the openshift client will attempt to load the default + configuration file from I(~/.kube/config.json). Can also be specified via K8S_AUTH_KUBECONFIG environment + variable. + context: + description: + - The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment + variable. + username: + description: + - Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME environment + variable. + password: + description: + - Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD environment + variable. + cert_file: + description: + - Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE + environment + variable. + key_file: + description: + - Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_HOST environment + variable. + ssl_ca_cert: + description: + - Path to a CA certificate used to authenticate with the API. Can also be specified via K8S_AUTH_SSL_CA_CERT + environment variable. + verify_ssl: + description: + - Whether or not to verify the API server's SSL certificates. Can also be specified via K8S_AUTH_VERIFY_SSL + environment variable. + type: bool + + requirements: + - "python >= 2.7" + - "openshift >= 0.3" + - "PyYAML >= 3.11" + + notes: + - "The OpenShift Python client wraps the K8s Python client, providing full access to + all of the APIS and models available on both platforms. For API version details and + additional information visit https://github.com/openshift/openshift-restclient-python" +""" + +EXAMPLES = """ +- name: Fetch a list of namespaces + set_fact: + projects: "{{ lookup('k8s', api_version='v1', kind='Namespace') }}" + +- name: Fetch all deployments + set_fact: + deployments: "{{ lookup('k8s', kind='Deployment', namespace='testing') }}" + +- name: Fetch all deployments in a namespace + set_fact: + deployments: "{{ lookup('k8s', kind='Deployment', namespace='testing') }}" + +- name: Fetch a specific deployment by name + set_fact: + deployments: "{{ lookup('k8s', kind='Deployment', namespace='testing', resource_name='elastic') }}" + +- name: Fetch with label selector + set_fact: + service: "{{ lookup('k8s', kind='Service', label_selector='app=galaxy') }}" + +# Use parameters from a YAML config + +- name: Load config from the Ansible controller filesystem + set_fact: + config: "{{ lookup('file', 'service.yml') | from_yaml }}" + +- name: Using the config (loaded from a file in prior task), fetch the latest version of the object + set_fact: + service: "{{ lookup('k8s', resource_definition=config) }}" + +- name: Use a config from the local filesystem + set_fact: + service: "{{ lookup('k8s', src='service.yml') }}" +""" + +RETURN = """ + _list: + description: + - One ore more object definitions returned from the API. + type: complex + contains: + api_version: + description: The versioned schema of this representation of an object. + returned: success + type: str + kind: + description: Represents the REST resource this object represents. + returned: success + type: str + metadata: + description: Standard object metadata. Includes name, namespace, annotations, labels, etc. + returned: success + type: complex + spec: + description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). + returned: success + type: complex + status: + description: Current status details for the object. + returned: success + type: complex +""" + +from ansible.plugins.lookup import LookupBase +from ansible.module_utils.k8s.lookup import KubernetesLookup + + +class LookupModule(LookupBase): + + def run(self, terms, variables=None, **kwargs): + return KubernetesLookup().run(terms, variables=variables, **kwargs) diff --git a/lib/ansible/plugins/lookup/openshift.py b/lib/ansible/plugins/lookup/openshift.py index 6753141971b..5c12e747a81 100644 --- a/lib/ansible/plugins/lookup/openshift.py +++ b/lib/ansible/plugins/lookup/openshift.py @@ -1,248 +1,197 @@ -# -*- 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) +# +# Copyright 2018 Red Hat | Ansible +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . 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 + + short_description: Query the OpenShift API + 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. + - Uses the OpenShift Python client to fetch a specific object by name, all matching objects within a + namespace, or all matching objects for all namespaces. + - Provides access the full range of K8s APIs. + - Enables authentication via config file, certificates, password or token. + options: + api_version: + description: + - Use to specify the API version. If I(resource definition) is provided, the I(apiVersion) from the + I(resource_definition) will override this option. + default: v1 kind: description: - - The kind of OpenShift resource to read (e.g. Project, Service, Pod) - required: True + - Use to specify an object model. If I(resource definition) is provided, the I(kind) from a + I(resource_definition) will override this option. + required: true + resource_name: + description: + - Fetch a specific object by name. If I(resource definition) is provided, the I(metadata.name) value + from the I(resource_definition) will override this option. + namespace: + description: + - Limit the objects returned to a specific namespace. If I(resource definition) is provided, the + I(metadata.namespace) value from the I(resource_definition) will override this option. + label_selector: + description: + - Additional labels to include in the query. Ignored when I(resource_name) is provided. + field_selector: + description: + - Specific fields on which to query. Ignored when I(resource_name) is provided. + resource_definition: + description: + - "Provide a YAML configuration for an object. NOTE: I(kind), I(api_version), I(resource_name), I(namespace), + and I(resource_version) will be overwritten by corresponding values found in the provided + I(resource_definition)." + src: + description: + - "Provide a path to a file containing a valid YAML definition of an object dated. Mutually + exclusive with I(resource_definition). NOTE: I(kind), I(api_version), I(resource_name), and I(namespace) + will be overwritten by corresponding values found in the configuration read in from the I(src) file." + - Reads from the local file system. To read from the Ansible controller's file system, use the file lookup + plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to + I(resource_definition). See Examples below. host: description: - - The IP address of the host serving the OpenShift API - required: False - default: 127.0.0.1 - port: + - Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable. + api_key: description: - - The port on which to access the OpenShift API - required: False - default: 8443 - token: + - Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment variable. + kubeconfig: description: - - The token to use for authentication against the OpenShift API. - - This can be a user or ServiceAccount token. - required: True - validate_certs: + - Path to an existing Kubernetes config file. If not provided, and no other connection + options are provided, the openshift client will attempt to load the default + configuration file from I(~/.kube/config.json). Can also be specified via K8S_AUTH_KUBECONFIG environment + variable. + context: description: - - Whether or not to validate the TLS certificate of the API. - required: False - default: True - namespace: + - The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment + variable. + username: description: - - The namespace/project where the object resides. - required: False - resource_name: + - Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME environment + variable. + password: description: - - The name of the object to query. - required: False - pretty: + - Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD environment + variable. + cert_file: description: - - Whether or not to prettify the output. This is useful for debugging. - required: False - default: False - labelSelector: + - Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE + environment variable. + key_file: description: - - Additional labels to include in the query. - required: False - fieldSelector: + - Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_HOST environment + variable. + ssl_ca_cert: description: - - Specific fields on which to query. - required: False - resourceVersion: + - Path to a CA certificate used to authenticate with the API. Can also be specified via K8S_AUTH_SSL_CA_CERT + environment variable. + verify_ssl: description: - - Query for a specific resource version. - required: False + - Whether or not to verify the API server's SSL certificates. Can also be specified via K8S_AUTH_VERIFY_SSL + environment variable. + type: bool + + requirements: + - "python >= 2.7" + - "openshift >= 0.3" + - "PyYAML >= 3.11" + + notes: + - "The OpenShift Python client wraps the K8s Python client, providing full access to + all of the APIS and models available on both platforms. For API version details and + additional information visit https://github.com/openshift/openshift-restclient-python" """ EXAMPLES = """ -- name: Get Project {{ project_name }} +- name: Fetch a list of projects 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 + projects: "{{ lookup('openshift', api_version='v1', kind='Project') }}" + +- name: Fetch all deployments + set_fact: + deployments: "{{ lookup('openshift', kind='DeploymentConfig', namespace='testing') }}" + +- name: Fetch all deployments in a namespace + set_fact: + deployments: "{{ lookup('openshift', kind='DeploymentConfig', namespace='testing') }}" + +- name: Fetch a specific deployment by name + set_fact: + deployments: "{{ lookup('openshift', kind='DeploymentConfig', namespace='testing', resource_name='elastic') }}" + +- name: Fetch with label selector + set_fact: + service: "{{ lookup('openshift', kind='Service', label_selector='app=galaxy') }}" + +# Use parameters from a YAML config + +- name: Load config from the Ansible controller filesystem + set_fact: + config: "{{ lookup('file', 'service.yml') | from_yaml }}" + +- name: Using the config (loaded from a file in prior task), fetch the latest version of the object 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) }}" + service: "{{ lookup('openshift', resource_definition=config) }}" + +- name: Use a config from the local filesystem + set_fact: + service: "{{ lookup('openshift', src='service.yml') }}" """ RETURN = """ _list: description: - - An object definition or list of objects definitions returned from OpenShift. - type: dict + - One or more object definitions returned from the API. + type: complex + contains: + api_version: + description: The versioned schema of this representation of an object. + returned: success + type: str + kind: + description: Represents the REST resource this object represents. + returned: success + type: str + metadata: + description: Standard object metadata. Includes name, namespace, annotations, labels, etc. + returned: success + type: complex + spec: + description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind). + returned: success + type: complex + status: + description: Current status details for the object. + returned: success + type: complex """ -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')) +from ansible.module_utils.k8s.lookup import OpenShiftLookup 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 + return OpenShiftLookup().run(terms, variables=variables, **kwargs)