From 4a8b4ead2f799844306b19ef7a5a7fea568d16bb Mon Sep 17 00:00:00 2001 From: Felix Matouschek Date: Mon, 28 Apr 2025 15:41:31 +0200 Subject: [PATCH] feat(inventory): Use OCP projects if available If no namespaces were specified in the inventory config try to get all available namespaces by trying to list OCP projects first. If the resource was not found (no OCP cluster) fall back to regular namespaces. Signed-off-by: Felix Matouschek --- plugins/inventory/kubevirt.py | 18 ++++++++++++++-- tests/unit/plugins/inventory/conftest.py | 17 +++++++++++++++ .../inventory/test_kubevirt_get_resources.py | 21 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/plugins/inventory/kubevirt.py b/plugins/inventory/kubevirt.py index 9ff5df1..aa2e5bc 100644 --- a/plugins/inventory/kubevirt.py +++ b/plugins/inventory/kubevirt.py @@ -141,7 +141,7 @@ from typing import ( # Set HAS_K8S_MODULE_HELPER and k8s_import exception accordingly to # potentially print a warning to the user if the client is missing. try: - from kubernetes.dynamic.exceptions import DynamicApiError + from kubernetes.dynamic.exceptions import DynamicApiError, ResourceNotFoundError HAS_K8S_MODULE_HELPER = True K8S_IMPORT_EXCEPTION = None @@ -152,6 +152,11 @@ except ImportError as e: Dummy class, mainly used for ansible-test sanity. """ + class ResourceNotFoundError(Exception): + """ + Dummy class, mainly used for ansible-test sanity. + """ + HAS_K8S_MODULE_HELPER = False K8S_IMPORT_EXCEPTION = e @@ -575,9 +580,18 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): _get_available_namespaces lists all namespaces accessible with the configured credentials and returns them. """ + + namespaces = [] + try: + namespaces = self._get_resources( + client, "project.openshift.io/v1", "Project" + ) + except ResourceNotFoundError: + namespaces = self._get_resources(client, "v1", "Namespace") + return [ namespace["metadata"]["name"] - for namespace in self._get_resources(client, "v1", "Namespace") + for namespace in namespaces if "metadata" in namespace and "name" in namespace["metadata"] ] diff --git a/tests/unit/plugins/inventory/conftest.py b/tests/unit/plugins/inventory/conftest.py index 3f133e4..5a5602c 100644 --- a/tests/unit/plugins/inventory/conftest.py +++ b/tests/unit/plugins/inventory/conftest.py @@ -8,6 +8,7 @@ __metaclass__ = type import pytest +from kubernetes.dynamic.exceptions import ResourceNotFoundError from kubernetes.dynamic.resource import ResourceField from ansible.template import Templar @@ -113,6 +114,9 @@ def client(mocker, request): dns_obj = ResourceField({"spec": {"baseDomain": base_domain}}) dns.items = [dns_obj] + projects = mocker.Mock() + projects.items = [ResourceField(item) for item in param.get("projects", [])] + namespace_client = mocker.Mock() namespace_client.get = mocker.Mock(return_value=namespaces) vm_client = mocker.Mock() @@ -130,6 +134,14 @@ def client(mocker, request): dns_client = mocker.Mock() dns_client.get = dns_client_get + def project_client_get(): + if not projects.items: + raise ResourceNotFoundError + return projects + + project_client = mocker.Mock() + project_client.get = project_client_get + def resources_get(api_version="", kind=""): if api_version.lower() == "v1": if kind.lower() == "namespace": @@ -138,6 +150,11 @@ def client(mocker, request): return service_client elif api_version.lower() == "config.openshift.io/v1" and kind.lower() == "dns": return dns_client + elif ( + api_version.lower() == "project.openshift.io/v1" + and kind.lower() == "project" + ): + return project_client elif "kubevirt.io/" in api_version.lower(): if kind.lower() == "virtualmachine": return vm_client diff --git a/tests/unit/plugins/inventory/test_kubevirt_get_resources.py b/tests/unit/plugins/inventory/test_kubevirt_get_resources.py index 9b71f59..aaf8ceb 100644 --- a/tests/unit/plugins/inventory/test_kubevirt_get_resources.py +++ b/tests/unit/plugins/inventory/test_kubevirt_get_resources.py @@ -63,6 +63,27 @@ def test_get_resources(inventory, client): }, [DEFAULT_NAMESPACE, "test"], ), + ( + { + "projects": [ + {"metadata": {"name": DEFAULT_NAMESPACE}}, + {"metadata": {"name": "testproject"}}, + ] + }, + [DEFAULT_NAMESPACE, "testproject"], + ), + ( + { + "namespaces": [ + {"metadata": {"name": DEFAULT_NAMESPACE}}, + {"metadata": {"name": "test"}}, + ], + "projects": [ + {"metadata": {"name": "testproject"}}, + ], + }, + ["testproject"], + ), ], indirect=["client"], )