From 2fd18c77aef019a34b79b12a58fcbe40272cda8d Mon Sep 17 00:00:00 2001 From: Pilou Date: Thu, 27 Sep 2018 00:16:54 +0200 Subject: openshift inventory plugin: fix exception when auth fails (#45826) * openshift inventory: fix exception when auth fails Fix 'ForbiddenError' object has no attribute 'message': [WARNING]: * Failed to parse test.yml with openshift plugin: 'ForbiddenError' object has no attribute 'message' File "ansible/lib/ansible/inventory/manager.py", line 270, in parse_source plugin.parse(self._inventory, self._loader, source, cache=cache) File "ansible/lib/ansible/plugins/inventory/openshift.py", line 122, in parse self.setup(config_data, cache, cache_key) File "ansible/lib/ansible/module_utils/k8s/inventory.py", line 58, in setup self.fetch_objects(connections) File "ansible/lib/ansible/module_utils/k8s/inventory.py", line 250, in fetch_objects super(OpenShiftInventoryHelper, self).fetch_objects(connections) File "ansible/lib/ansible/module_utils/k8s/inventory.py", line 81, in fetch_objects namespaces = self.get_available_namespaces(client) File "ansible/lib/ansible/module_utils/k8s/inventory.py", line 95, in get_available_namespaces raise K8sInventoryException('Error fetching Namespace list: {0}'.format(exc.message)) Don't try to get 'message' attribute from: - K8sInventoryException instances - Exception instances - KubernetesException instances (because KubernetesException can be Exception) * move k8s/OpenShift inventory plugin dedicated code inventory plugin specific code should not be located in lib/ansible/module_utils directory. Then ansible.utils methods can be reused (for example Display). * Remove unused class variables 'helper' unused since 4d77878654e867c23d5f6c61422bdae7120393bc. --- lib/ansible/module_utils/k8s/common.py | 3 +- lib/ansible/module_utils/k8s/inventory.py | 316 ----------------------------- lib/ansible/module_utils/k8s/scale.py | 6 +- lib/ansible/plugins/inventory/k8s.py | 234 ++++++++++++++++++++- lib/ansible/plugins/inventory/openshift.py | 86 +++++++- 5 files changed, 315 insertions(+), 330 deletions(-) delete mode 100644 lib/ansible/module_utils/k8s/inventory.py diff --git a/lib/ansible/module_utils/k8s/common.py b/lib/ansible/module_utils/k8s/common.py index ae82f32370..17be0b297f 100644 --- a/lib/ansible/module_utils/k8s/common.py +++ b/lib/ansible/module_utils/k8s/common.py @@ -17,8 +17,9 @@ from __future__ import absolute_import, division, print_function -import os import copy +import json +import os from ansible.module_utils.basic import AnsibleModule diff --git a/lib/ansible/module_utils/k8s/inventory.py b/lib/ansible/module_utils/k8s/inventory.py deleted file mode 100644 index 8cd954d020..0000000000 --- a/lib/ansible/module_utils/k8s/inventory.py +++ /dev/null @@ -1,316 +0,0 @@ -# -# 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 - -from ansible.module_utils.k8s.common import K8sAnsibleMixin, HAS_K8S_MODULE_HELPER - -try: - from ansible.errors import AnsibleError -except ImportError: - AnsibleError = Exception - -try: - from openshift.dynamic.exceptions import DynamicApiError -except ImportError: - pass - - -class K8sInventoryException(Exception): - pass - - -class K8sInventoryHelper(K8sAnsibleMixin): - helper = None - transport = 'kubectl' - - def setup(self, config_data, cache, cache_key): - connections = config_data.get('connections') - - if not HAS_K8S_MODULE_HELPER: - raise K8sInventoryException( - "This module requires the OpenShift Python client. Try `pip install openshift`" - ) - - source_data = None - if cache and cache_key in self._cache: - try: - source_data = self._cache[cache_key] - except KeyError: - pass - - if not source_data: - self.fetch_objects(connections) - - def fetch_objects(self, connections): - client = self.get_api_client() - - if connections: - if not isinstance(connections, list): - raise K8sInventoryException("Expecting connections to be a list.") - - for connection in connections: - if not isinstance(connection, dict): - raise K8sInventoryException("Expecting connection to be a dictionary.") - client = self.get_api_client(**connection) - name = connection.get('name', self.get_default_host_name(client.configuration.host)) - if connection.get('namespaces'): - namespaces = connection['namespaces'] - else: - namespaces = self.get_available_namespaces(client) - for namespace in namespaces: - self.get_pods_for_namespace(client, name, namespace) - self.get_services_for_namespace(client, name, namespace) - else: - name = self.get_default_host_name(client.configuration.host) - namespaces = self.get_available_namespaces(client) - for namespace in namespaces: - self.get_pods_for_namespace(client, name, namespace) - self.get_services_for_namespace(client, name, namespace) - - @staticmethod - def get_default_host_name(host): - return host.replace('https://', '').replace('http://', '').replace('.', '-').replace(':', '_') - - def get_available_namespaces(self, client): - v1_namespace = client.resources.get(api_version='v1', kind='Namespace') - try: - obj = v1_namespace.get() - except DynamicApiError as exc: - raise K8sInventoryException('Error fetching Namespace list: {0}'.format(exc.message)) - return [namespace.metadata.name for namespace in obj.items] - - def get_pods_for_namespace(self, client, name, namespace): - v1_pod = client.resources.get(api_version='v1', kind='Pod') - try: - obj = v1_pod.get(namespace=namespace) - except DynamicApiError as exc: - raise K8sInventoryException('Error fetching Pod list: {0}'.format(exc.message)) - - namespace_group = 'namespace_{0}'.format(namespace) - namespace_pods_group = '{0}_pods'.format(namespace_group) - - self.inventory.add_group(name) - self.inventory.add_group(namespace_group) - self.inventory.add_child(name, namespace_group) - self.inventory.add_group(namespace_pods_group) - self.inventory.add_child(namespace_group, namespace_pods_group) - - for pod in obj.items: - pod_name = pod.metadata.name - pod_groups = [] - pod_labels = {} if not pod.metadata.labels else pod.metadata.labels - pod_annotations = {} if not pod.metadata.annotations else pod.metadata.annotations - - if pod.metadata.labels: - pod_labels = pod.metadata.labels - # create a group for each label_value - for key, value in pod.metadata.labels: - group_name = 'label_{0}_{1}'.format(key, value) - if group_name not in pod_groups: - pod_groups.append(group_name) - self.inventory.add_group(group_name) - - for container in pod.status.containerStatuses: - # add each pod_container to the namespace group, and to each label_value group - container_name = '{0}_{1}'.format(pod.metadata.name, container.name) - self.inventory.add_host(container_name) - self.inventory.add_child(namespace_pods_group, container_name) - if pod_groups: - for group in pod_groups: - self.inventory.add_child(group, container_name) - - # Add hostvars - self.inventory.set_variable(container_name, 'object_type', 'pod') - self.inventory.set_variable(container_name, 'labels', pod_labels) - self.inventory.set_variable(container_name, 'annotations', pod_annotations) - self.inventory.set_variable(container_name, 'cluster_name', pod.metadata.clusterName) - self.inventory.set_variable(container_name, 'pod_node_name', pod.spec.nodeName) - self.inventory.set_variable(container_name, 'pod_name', pod.spec.name) - self.inventory.set_variable(container_name, 'pod_host_ip', pod.status.hostIP) - self.inventory.set_variable(container_name, 'pod_phase', pod.status.phase) - self.inventory.set_variable(container_name, 'pod_ip', pod.status.podIP) - self.inventory.set_variable(container_name, 'pod_self_link', pod.metadata.selfLink) - self.inventory.set_variable(container_name, 'pod_resource_version', pod.metadata.resourceVersion) - self.inventory.set_variable(container_name, 'pod_uid', pod.metadata.uid) - self.inventory.set_variable(container_name, 'container_name', container.image) - self.inventory.set_variable(container_name, 'container_image', container.image) - if container.state.running: - self.inventory.set_variable(container_name, 'container_state', 'Running') - if container.state.terminated: - self.inventory.set_variable(container_name, 'container_state', 'Terminated') - if container.state.waiting: - self.inventory.set_variable(container_name, 'container_state', 'Waiting') - self.inventory.set_variable(container_name, 'container_ready', container.ready) - self.inventory.set_variable(container_name, 'ansible_remote_tmp', '/tmp/') - self.inventory.set_variable(container_name, 'ansible_connection', self.transport) - self.inventory.set_variable(container_name, 'ansible_{0}_pod'.format(self.transport), - pod_name) - self.inventory.set_variable(container_name, 'ansible_{0}_container'.format(self.transport), - container.name) - self.inventory.set_variable(container_name, 'ansible_{0}_namespace'.format(self.transport), - namespace) - - def get_services_for_namespace(self, client, name, namespace): - v1_service = client.resources.get(api_version='v1', kind='Service') - try: - obj = v1_service.get(namespace=namespace) - except DynamicApiError as exc: - raise K8sInventoryException('Error fetching Service list: {0}'.format(exc.message)) - - namespace_group = 'namespace_{0}'.format(namespace) - namespace_services_group = '{0}_services'.format(namespace_group) - - self.inventory.add_group(name) - self.inventory.add_group(namespace_group) - self.inventory.add_child(name, namespace_group) - self.inventory.add_group(namespace_services_group) - self.inventory.add_child(namespace_group, namespace_services_group) - - for service in obj.items: - service_name = service.metadata.name - service_labels = {} if not service.metadata.labels else service.metadata.labels - service_annotations = {} if not service.metadata.annotations else service.metadata.annotations - - self.inventory.add_host(service_name) - - if service.metadata.labels: - # create a group for each label_value - for key, value in service.metadata.labels: - group_name = 'label_{0}_{1}'.format(key, value) - self.inventory.add_group(group_name) - self.inventory.add_child(group_name, service_name) - - try: - self.inventory.add_child(namespace_services_group, service_name) - except AnsibleError as e: - raise - - ports = [{'name': port.name, - 'port': port.port, - 'protocol': port.protocol, - 'targetPort': port.targetPort, - 'nodePort': port.nodePort} for port in service.spec.ports or []] - - # add hostvars - self.inventory.set_variable(service_name, 'object_type', 'service') - self.inventory.set_variable(service_name, 'labels', service_labels) - self.inventory.set_variable(service_name, 'annotations', service_annotations) - self.inventory.set_variable(service_name, 'cluster_name', service.metadata.clusterName) - self.inventory.set_variable(service_name, 'ports', ports) - self.inventory.set_variable(service_name, 'type', service.spec.type) - self.inventory.set_variable(service_name, 'self_link', service.metadata.selfLink) - self.inventory.set_variable(service_name, 'resource_version', service.metadata.resourceVersion) - self.inventory.set_variable(service_name, 'uid', service.metadata.uid) - - if service.spec.externalTrafficPolicy: - self.inventory.set_variable(service_name, 'external_traffic_policy', - service.spec.externalTrafficPolicy) - if service.spec.externalIPs: - self.inventory.set_variable(service_name, 'external_ips', service.spec.externalIPs) - - if service.spec.externalName: - self.inventory.set_variable(service_name, 'external_name', service.spec.externalName) - - if service.spec.healthCheckNodePort: - self.inventory.set_variable(service_name, 'health_check_node_port', - service.spec.healthCheckNodePort) - if service.spec.loadBalancerIP: - self.inventory.set_variable(service_name, 'load_balancer_ip', - service.spec.loadBalancerIP) - if service.spec.selector: - self.inventory.set_variable(service_name, 'selector', service.spec.selector) - - if hasattr(service.status.loadBalancer, 'ingress') and service.status.loadBalancer.ingress: - load_balancer = [{'hostname': ingress.hostname, - 'ip': ingress.ip} for ingress in service.status.loadBalancer.ingress] - self.inventory.set_variable(service_name, 'load_balancer', load_balancer) - - -class OpenShiftInventoryHelper(K8sInventoryHelper): - helper = None - transport = 'oc' - - def fetch_objects(self, connections): - super(OpenShiftInventoryHelper, self).fetch_objects(connections) - client = self.get_api_client() - - if connections: - for connection in connections: - client = self.get_api_client(**connection) - name = connection.get('name', self.get_default_host_name(client.configuration.host)) - if connection.get('namespaces'): - namespaces = connection['namespaces'] - else: - namespaces = self.get_available_namespaces(client) - for namespace in namespaces: - self.get_routes_for_namespace(client, name, namespace) - else: - name = self.get_default_host_name(client.configuration.host) - namespaces = self.get_available_namespaces(client) - for namespace in namespaces: - self.get_routes_for_namespace(client, name, namespace) - - def get_routes_for_namespace(self, client, name, namespace): - v1_route = client.resources.get(api_version='v1', kind='Route') - try: - obj = v1_route.get(namespace=namespace) - except DynamicApiError as exc: - raise K8sInventoryException('Error fetching Routes list: {0}'.format(exc.message)) - - namespace_group = 'namespace_{0}'.format(namespace) - namespace_routes_group = '{0}_routes'.format(namespace_group) - - self.inventory.add_group(name) - self.inventory.add_group(namespace_group) - self.inventory.add_child(name, namespace_group) - self.inventory.add_group(namespace_routes_group) - self.inventory.add_child(namespace_group, namespace_routes_group) - for route in obj.items: - route_name = route.metadata.name - route_labels = {} if not route.metadata.labels else route.metadata.labels - route_annotations = {} if not route.metadata.annotations else route.metadata.annotations - - self.inventory.add_host(route_name) - - if route.metadata.labels: - # create a group for each label_value - for key, value in route.metadata.labels: - group_name = 'label_{0}_{1}'.format(key, value) - self.inventory.add_group(group_name) - self.inventory.add_child(group_name, route_name) - - self.inventory.add_child(namespace_routes_group, route_name) - - # add hostvars - self.inventory.set_variable(route_name, 'labels', route_labels) - self.inventory.set_variable(route_name, 'annotations', route_annotations) - self.inventory.set_variable(route_name, 'cluster_name', route.metadata.clusterName) - self.inventory.set_variable(route_name, 'object_type', 'route') - self.inventory.set_variable(route_name, 'self_link', route.metadata.selfLink) - self.inventory.set_variable(route_name, 'resource_version', route.metadata.resourceVersion) - self.inventory.set_variable(route_name, 'uid', route.metadata.uid) - - if route.spec.host: - self.inventory.set_variable(route_name, 'host', route.spec.host) - - if route.spec.path: - self.inventory.set_variable(route_name, 'path', route.spec.path) - - if hasattr(route.spec.port, 'targetPort') and route.spec.port.targetPort: - self.inventory.set_variable(route_name, 'port', route.spec.port) diff --git a/lib/ansible/module_utils/k8s/scale.py b/lib/ansible/module_utils/k8s/scale.py index 9454cb95b2..73d3170046 100644 --- a/lib/ansible/module_utils/k8s/scale.py +++ b/lib/ansible/module_utils/k8s/scale.py @@ -70,7 +70,7 @@ class KubernetesAnsibleScaleModule(KubernetesRawModule): existing = resource.get(name=name, namespace=namespace) return_attributes['result'] = existing.to_dict() except KubernetesException as exc: - self.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc.message), + self.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc), error=exc.value.get('status')) if self.kind == 'job': @@ -129,7 +129,7 @@ class KubernetesAnsibleScaleModule(KubernetesRawModule): resource.scale.patch(body=scale_obj) except Exception as exc: self.fail_json( - msg="Scale request failed: {0}".format(exc.message) + msg="Scale request failed: {0}".format(exc) ) if wait and stream is not None: @@ -173,7 +173,7 @@ class KubernetesAnsibleScaleModule(KubernetesRawModule): watcher.stop() break except Exception as exc: - self.fail_json(msg="Exception reading event stream: {0}".format(exc.message)) + self.fail_json(msg="Exception reading event stream: {0}".format(exc)) if not return_obj: self.fail_json(msg="Error fetching the patched object. Try a higher wait_timeout value.") diff --git a/lib/ansible/plugins/inventory/k8s.py b/lib/ansible/plugins/inventory/k8s.py index d6fcc1e592..6012a12052 100644 --- a/lib/ansible/plugins/inventory/k8s.py +++ b/lib/ansible/plugins/inventory/k8s.py @@ -108,15 +108,245 @@ connections: context: 'awx/192-168-64-4:8443/developer' ''' +import json + +from ansible.errors import AnsibleError +from ansible.module_utils.k8s.common import K8sAnsibleMixin, HAS_K8S_MODULE_HELPER from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable -from ansible.module_utils.k8s.inventory import K8sInventoryHelper +try: + from openshift.dynamic.exceptions import DynamicApiError +except ImportError: + pass + + +def format_dynamic_api_exc(exc): + if exc.body: + if exc.headers and exc.headers.get('Content-Type') == 'application/json': + message = json.loads(exc.body).get('message') + if message: + return message + return exc.body + else: + return '%s Reason: %s' % (exc.status, exc.reason) + + +class K8sInventoryException(Exception): + pass -class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sInventoryHelper): + +class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleMixin): NAME = 'k8s' + transport = 'kubectl' + def parse(self, inventory, loader, path, cache=True): super(InventoryModule, self).parse(inventory, loader, path) cache_key = self._get_cache_prefix(path) config_data = self._read_config_data(path) self.setup(config_data, cache, cache_key) + + def setup(self, config_data, cache, cache_key): + connections = config_data.get('connections') + + if not HAS_K8S_MODULE_HELPER: + raise K8sInventoryException( + "This module requires the OpenShift Python client. Try `pip install openshift`" + ) + + source_data = None + if cache and cache_key in self._cache: + try: + source_data = self._cache[cache_key] + except KeyError: + pass + + if not source_data: + self.fetch_objects(connections) + + def fetch_objects(self, connections): + client = self.get_api_client() + + if connections: + if not isinstance(connections, list): + raise K8sInventoryException("Expecting connections to be a list.") + + for connection in connections: + if not isinstance(connection, dict): + raise K8sInventoryException("Expecting connection to be a dictionary.") + client = self.get_api_client(**connection) + name = connection.get('name', self.get_default_host_name(client.configuration.host)) + if connection.get('namespaces'): + namespaces = connection['namespaces'] + else: + namespaces = self.get_available_namespaces(client) + for namespace in namespaces: + self.get_pods_for_namespace(client, name, namespace) + self.get_services_for_namespace(client, name, namespace) + else: + name = self.get_default_host_name(client.configuration.host) + namespaces = self.get_available_namespaces(client) + for namespace in namespaces: + self.get_pods_for_namespace(client, name, namespace) + self.get_services_for_namespace(client, name, namespace) + + @staticmethod + def get_default_host_name(host): + return host.replace('https://', '').replace('http://', '').replace('.', '-').replace(':', '_') + + def get_available_namespaces(self, client): + v1_namespace = client.resources.get(api_version='v1', kind='Namespace') + try: + obj = v1_namespace.get() + except DynamicApiError as exc: + self.display.debug(exc) + raise K8sInventoryException('Error fetching Namespace list: %s' % format_dynamic_api_exc(exc)) + return [namespace.metadata.name for namespace in obj.items] + + def get_pods_for_namespace(self, client, name, namespace): + v1_pod = client.resources.get(api_version='v1', kind='Pod') + try: + obj = v1_pod.get(namespace=namespace) + except DynamicApiError as exc: + self.display.debug(exc) + raise K8sInventoryException('Error fetching Pod list: %s' % format_dynamic_api_exc(exc)) + + namespace_group = 'namespace_{0}'.format(namespace) + namespace_pods_group = '{0}_pods'.format(namespace_group) + + self.inventory.add_group(name) + self.inventory.add_group(namespace_group) + self.inventory.add_child(name, namespace_group) + self.inventory.add_group(namespace_pods_group) + self.inventory.add_child(namespace_group, namespace_pods_group) + + for pod in obj.items: + pod_name = pod.metadata.name + pod_groups = [] + pod_labels = {} if not pod.metadata.labels else pod.metadata.labels + pod_annotations = {} if not pod.metadata.annotations else pod.metadata.annotations + + if pod.metadata.labels: + pod_labels = pod.metadata.labels + # create a group for each label_value + for key, value in pod.metadata.labels: + group_name = 'label_{0}_{1}'.format(key, value) + if group_name not in pod_groups: + pod_groups.append(group_name) + self.inventory.add_group(group_name) + + for container in pod.status.containerStatuses: + # add each pod_container to the namespace group, and to each label_value group + container_name = '{0}_{1}'.format(pod.metadata.name, container.name) + self.inventory.add_host(container_name) + self.inventory.add_child(namespace_pods_group, container_name) + if pod_groups: + for group in pod_groups: + self.inventory.add_child(group, container_name) + + # Add hostvars + self.inventory.set_variable(container_name, 'object_type', 'pod') + self.inventory.set_variable(container_name, 'labels', pod_labels) + self.inventory.set_variable(container_name, 'annotations', pod_annotations) + self.inventory.set_variable(container_name, 'cluster_name', pod.metadata.clusterName) + self.inventory.set_variable(container_name, 'pod_node_name', pod.spec.nodeName) + self.inventory.set_variable(container_name, 'pod_name', pod.spec.name) + self.inventory.set_variable(container_name, 'pod_host_ip', pod.status.hostIP) + self.inventory.set_variable(container_name, 'pod_phase', pod.status.phase) + self.inventory.set_variable(container_name, 'pod_ip', pod.status.podIP) + self.inventory.set_variable(container_name, 'pod_self_link', pod.metadata.selfLink) + self.inventory.set_variable(container_name, 'pod_resource_version', pod.metadata.resourceVersion) + self.inventory.set_variable(container_name, 'pod_uid', pod.metadata.uid) + self.inventory.set_variable(container_name, 'container_name', container.image) + self.inventory.set_variable(container_name, 'container_image', container.image) + if container.state.running: + self.inventory.set_variable(container_name, 'container_state', 'Running') + if container.state.terminated: + self.inventory.set_variable(container_name, 'container_state', 'Terminated') + if container.state.waiting: + self.inventory.set_variable(container_name, 'container_state', 'Waiting') + self.inventory.set_variable(container_name, 'container_ready', container.ready) + self.inventory.set_variable(container_name, 'ansible_remote_tmp', '/tmp/') + self.inventory.set_variable(container_name, 'ansible_connection', self.transport) + self.inventory.set_variable(container_name, 'ansible_{0}_pod'.format(self.transport), + pod_name) + self.inventory.set_variable(container_name, 'ansible_{0}_container'.format(self.transport), + container.name) + self.inventory.set_variable(container_name, 'ansible_{0}_namespace'.format(self.transport), + namespace) + + def get_services_for_namespace(self, client, name, namespace): + v1_service = client.resources.get(api_version='v1', kind='Service') + try: + obj = v1_service.get(namespace=namespace) + except DynamicApiError as exc: + self.display.debug(exc) + raise K8sInventoryException('Error fetching Service list: %s' % format_dynamic_api_exc(exc)) + + namespace_group = 'namespace_{0}'.format(namespace) + namespace_services_group = '{0}_services'.format(namespace_group) + + self.inventory.add_group(name) + self.inventory.add_group(namespace_group) + self.inventory.add_child(name, namespace_group) + self.inventory.add_group(namespace_services_group) + self.inventory.add_child(namespace_group, namespace_services_group) + + for service in obj.items: + service_name = service.metadata.name + service_labels = {} if not service.metadata.labels else service.metadata.labels + service_annotations = {} if not service.metadata.annotations else service.metadata.annotations + + self.inventory.add_host(service_name) + + if service.metadata.labels: + # create a group for each label_value + for key, value in service.metadata.labels: + group_name = 'label_{0}_{1}'.format(key, value) + self.inventory.add_group(group_name) + self.inventory.add_child(group_name, service_name) + + try: + self.inventory.add_child(namespace_services_group, service_name) + except AnsibleError as e: + raise + + ports = [{'name': port.name, + 'port': port.port, + 'protocol': port.protocol, + 'targetPort': port.targetPort, + 'nodePort': port.nodePort} for port in service.spec.ports or []] + + # add hostvars + self.inventory.set_variable(service_name, 'object_type', 'service') + self.inventory.set_variable(service_name, 'labels', service_labels) + self.inventory.set_variable(service_name, 'annotations', service_annotations) + self.inventory.set_variable(service_name, 'cluster_name', service.metadata.clusterName) + self.inventory.set_variable(service_name, 'ports', ports) + self.inventory.set_variable(service_name, 'type', service.spec.type) + self.inventory.set_variable(service_name, 'self_link', service.metadata.selfLink) + self.inventory.set_variable(service_name, 'resource_version', service.metadata.resourceVersion) + self.inventory.set_variable(service_name, 'uid', service.metadata.uid) + + if service.spec.externalTrafficPolicy: + self.inventory.set_variable(service_name, 'external_traffic_policy', + service.spec.externalTrafficPolicy) + if service.spec.externalIPs: + self.inventory.set_variable(service_name, 'external_ips', service.spec.externalIPs) + + if service.spec.externalName: + self.inventory.set_variable(service_name, 'external_name', service.spec.externalName) + + if service.spec.healthCheckNodePort: + self.inventory.set_variable(service_name, 'health_check_node_port', + service.spec.healthCheckNodePort) + if service.spec.loadBalancerIP: + self.inventory.set_variable(service_name, 'load_balancer_ip', + service.spec.loadBalancerIP) + if service.spec.selector: + self.inventory.set_variable(service_name, 'selector', service.spec.selector) + + if hasattr(service.status.loadBalancer, 'ingress') and service.status.loadBalancer.ingress: + load_balancer = [{'hostname': ingress.hostname, + 'ip': ingress.ip} for ingress in service.status.loadBalancer.ingress] + self.inventory.set_variable(service_name, 'load_balancer', load_balancer) diff --git a/lib/ansible/plugins/inventory/openshift.py b/lib/ansible/plugins/inventory/openshift.py index 97e3053f1d..d9e7602ce2 100644 --- a/lib/ansible/plugins/inventory/openshift.py +++ b/lib/ansible/plugins/inventory/openshift.py @@ -108,15 +108,85 @@ connections: context: 'awx/192-168-64-4:8443/developer' ''' -from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable -from ansible.module_utils.k8s.inventory import OpenShiftInventoryHelper +from ansible.plugins.inventory.k8s import K8sInventoryException, InventoryModule as K8sInventoryModule, format_dynamic_api_exc +try: + from openshift.dynamic.exceptions import DynamicApiError +except ImportError: + pass -class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, OpenShiftInventoryHelper): + +class InventoryModule(K8sInventoryModule): NAME = 'openshift' - def parse(self, inventory, loader, path, cache=True): - super(InventoryModule, self).parse(inventory, loader, path) - cache_key = self._get_cache_prefix(path) - config_data = self._read_config_data(path) - self.setup(config_data, cache, cache_key) + transport = 'oc' + + def fetch_objects(self, connections): + super(InventoryModule, self).fetch_objects(connections) + client = self.get_api_client() + + if connections: + for connection in connections: + client = self.get_api_client(**connection) + name = connection.get('name', self.get_default_host_name(client.configuration.host)) + if connection.get('namespaces'): + namespaces = connection['namespaces'] + else: + namespaces = self.get_available_namespaces(client) + for namespace in namespaces: + self.get_routes_for_namespace(client, name, namespace) + else: + name = self.get_default_host_name(client.configuration.host) + namespaces = self.get_available_namespaces(client) + for namespace in namespaces: + self.get_routes_for_namespace(client, name, namespace) + + def get_routes_for_namespace(self, client, name, namespace): + v1_route = client.resources.get(api_version='v1', kind='Route') + try: + obj = v1_route.get(namespace=namespace) + except DynamicApiError as exc: + self.display.debug(exc) + raise K8sInventoryException('Error fetching Routes list: %s' % format_dynamic_api_exc(exc)) + + namespace_group = 'namespace_{0}'.format(namespace) + namespace_routes_group = '{0}_routes'.format(namespace_group) + + self.inventory.add_group(name) + self.inventory.add_group(namespace_group) + self.inventory.add_child(name, namespace_group) + self.inventory.add_group(namespace_routes_group) + self.inventory.add_child(namespace_group, namespace_routes_group) + for route in obj.items: + route_name = route.metadata.name + route_labels = {} if not route.metadata.labels else route.metadata.labels + route_annotations = {} if not route.metadata.annotations else route.metadata.annotations + + self.inventory.add_host(route_name) + + if route.metadata.labels: + # create a group for each label_value + for key, value in route.metadata.labels: + group_name = 'label_{0}_{1}'.format(key, value) + self.inventory.add_group(group_name) + self.inventory.add_child(group_name, route_name) + + self.inventory.add_child(namespace_routes_group, route_name) + + # add hostvars + self.inventory.set_variable(route_name, 'labels', route_labels) + self.inventory.set_variable(route_name, 'annotations', route_annotations) + self.inventory.set_variable(route_name, 'cluster_name', route.metadata.clusterName) + self.inventory.set_variable(route_name, 'object_type', 'route') + self.inventory.set_variable(route_name, 'self_link', route.metadata.selfLink) + self.inventory.set_variable(route_name, 'resource_version', route.metadata.resourceVersion) + self.inventory.set_variable(route_name, 'uid', route.metadata.uid) + + if route.spec.host: + self.inventory.set_variable(route_name, 'host', route.spec.host) + + if route.spec.path: + self.inventory.set_variable(route_name, 'path', route.spec.path) + + if hasattr(route.spec.port, 'targetPort') and route.spec.port.targetPort: + self.inventory.set_variable(route_name, 'port', route.spec.port) -- cgit v1.2.1