diff --git a/examples/inventory.kubevirt.yml b/examples/inventory.kubevirt.yml index eb9480c..63d5b3a 100644 --- a/examples/inventory.kubevirt.yml +++ b/examples/inventory.kubevirt.yml @@ -1,7 +1,6 @@ --- plugin: kubevirt.core.kubevirt -connections: - - namespaces: - - default - network_name: bridge-network - label_selector: app=test +namespaces: + - default +network_name: bridge-network +label_selector: app=test diff --git a/examples/kubesecondarydns.kubevirt.yml b/examples/kubesecondarydns.kubevirt.yml index 04b77d2..ab153b3 100644 --- a/examples/kubesecondarydns.kubevirt.yml +++ b/examples/kubesecondarydns.kubevirt.yml @@ -1,7 +1,6 @@ --- plugin: kubevirt.core.kubevirt -connections: - - namespaces: - - default - network_name: bridge-network - kube_secondary_dns: true +namespaces: + - default +network_name: bridge-network +kube_secondary_dns: true diff --git a/examples/services.kubevirt.yml b/examples/services.kubevirt.yml index 253631f..bd8ba6e 100644 --- a/examples/services.kubevirt.yml +++ b/examples/services.kubevirt.yml @@ -1,6 +1,5 @@ --- plugin: kubevirt.core.kubevirt -connections: - - namespaces: - - default - use_service: true +namespaces: + - default +use_service: true diff --git a/plugins/inventory/kubevirt.py b/plugins/inventory/kubevirt.py index 190cfbe..46aaed3 100644 --- a/plugins/inventory/kubevirt.py +++ b/plugins/inventory/kubevirt.py @@ -17,11 +17,13 @@ author: description: - Fetch virtual machines from one or more namespaces with an optional label selector. -- Groups by cluster name, namespace and labels. -- Uses the M(kubernetes.core.kubectl) connection plugin to access the Kubernetes cluster. +- Groups by cluster name, namespaces and labels. - Uses V(*.kubevirt.[yml|yaml]) YAML configuration file to set parameter values. +- By default it uses the active context in I(~/.kube/config) and will return all virtual machines + for all namespaces the active user is authorized to access. extends_documentation_fragment: +- kubevirt.core.kubevirt_auth_options - inventory_cache - constructed @@ -34,108 +36,57 @@ options: description: - 'Specify the format of the host in the inventory group. Available specifiers: V(name), V(namespace) and V(uid).' default: "{namespace}-{name}" + name: + description: + - Optional name to assign to the cluster. If not provided, a name is constructed from the server + and port. + namespaces: + description: + - List of namespaces. If not specified, will fetch virtual machines from all namespaces + the user is authorized to access. + label_selector: + description: + - Define a label selector to select a subset of the fetched virtual machines. + network_name: + description: + - In case multiple networks are attached to a virtual machine, define which interface should + be returned as primary IP address. + aliases: [ interface_name ] + kube_secondary_dns: + description: + - Enable C(kubesecondarydns) derived host names when using a secondary network interface. + type: bool + default: False + use_service: + description: + - Enable the use of C(Services) to establish an SSH connection to a virtual machine. + - Services are only used if no O(network_name) was provided. + type: bool + default: True + create_groups: + description: + - Enable the creation of groups from labels on C(VirtualMachines) and C(VirtualMachineInstances). + type: bool + default: False + base_domain: + description: + - Override the base domain used to construct host names. Used in case of + C(kubesecondarydns) or C(Services) of type C(NodePort) if O(append_base_domain) is set. + append_base_domain: + description: + - Append the base domain of the cluster to host names constructed from SSH C(Services) of type C(NodePort). + type: bool + default: False + api_version: + description: + - Specify the used KubeVirt API version. + default: "kubevirt.io/v1" connections: description: - - Optional list of cluster connection settings. If no connections are provided, the default - I(~/.kube/config) and active context will be used, and objects will be returned for all namespaces - the active user is authorized to access. + - Optional list of cluster connection settings. - This parameter is deprecated. Split your connections into multiple configuration files and move parameters of each connection to the configuration top level. - Deprecated in version C(1.5.0), will be removed in version C(3.0.0). - type: list - elements: dict - suboptions: - name: - description: - - Optional name to assign to the cluster. If not provided, a name is constructed from the server - and port. - kubeconfig: - description: - - Path to an existing Kubernetes config file. If not provided, and no other connection - options are provided, the Kubernetes client will attempt to load the default - configuration file from I(~/.kube/config). Can also be specified via E(K8S_AUTH_KUBECONFIG) - environment variable. - context: - description: - - The name of a context found in the config file. Can also be specified via E(K8S_AUTH_CONTEXT) environment - variable. - host: - description: - - Provide a URL for accessing the API. Can also be specified via E(K8S_AUTH_HOST) environment variable. - api_key: - description: - - Token used to authenticate with the API. Can also be specified via E(K8S_AUTH_API_KEY) environment - variable. - username: - description: - - Provide a username for authenticating with the API. Can also be specified via E(K8S_AUTH_USERNAME) - environment variable. - password: - description: - - Provide a password for authenticating with the API. Can also be specified via E(K8S_AUTH_PASSWORD) - environment variable. - client_cert: - description: - - Path to a certificate used to authenticate with the API. Can also be specified via E(K8S_AUTH_CERT_FILE) - environment variable. - aliases: [ cert_file ] - client_key: - description: - - Path to a key file used to authenticate with the API. Can also be specified via E(K8S_AUTH_KEY_FILE) - environment variable. - aliases: [ key_file ] - ca_cert: - description: - - Path to a CA certificate used to authenticate with the API. Can also be specified via - E(K8S_AUTH_SSL_CA_CERT) environment variable. - aliases: [ ssl_ca_cert ] - validate_certs: - description: - - Whether or not to verify the API server's SSL certificates. Can also be specified via - E(K8S_AUTH_VERIFY_SSL) environment variable. - type: bool - aliases: [ verify_ssl ] - namespaces: - description: - - List of namespaces. If not specified, will fetch virtual machines from all namespaces - the user is authorized to access. - label_selector: - description: - - Define a label selector to select a subset of the fetched virtual machines. - network_name: - description: - - In case multiple networks are attached to a virtual machine, define which interface should - be returned as primary IP address. - aliases: [ interface_name ] - kube_secondary_dns: - description: - - Enable C(kubesecondarydns) derived host names when using a secondary network interface. - type: bool - default: False - use_service: - description: - - Enable the use of C(Services) to establish an SSH connection to a virtual machine. - - Services are only used if no O(connections.network_name) was provided. - type: bool - default: True - create_groups: - description: - - Enable the creation of groups from labels on C(VirtualMachines) and C(VirtualMachineInstances). - type: bool - default: False - base_domain: - description: - - Override the base domain used to construct host names. Used in case of - C(kubesecondarydns) or C(Services) of type C(NodePort) if O(connections.append_base_domain) is set. - append_base_domain: - description: - - Append the base domain of the cluster to host names constructed from SSH C(Services) of type C(NodePort). - type: bool - default: False - api_version: - description: - - Specify the used KubeVirt API version. - default: "kubevirt.io/v1" requirements: - "python >= 3.9" @@ -146,35 +97,31 @@ requirements: EXAMPLES = """ # Filename must end with kubevirt.[yml|yaml] -- name: Authenticate with token and return all virtual machines from all accessible namespaces - plugin: kubevirt.core.kubevirt - connections: - - host: https://192.168.64.4:8443 - api_key: xxxxxxxxxxxxxxxx - validate_certs: false +# Authenticate with token and return all virtual machines from all accessible namespaces +- plugin: kubevirt.core.kubevirt + host: https://192.168.64.4:8443 + api_key: xxxxxxxxxxxxxxxx + validate_certs: false -- name: Use default ~/.kube/config and return virtual machines from namespace testing connected to network bridge-network - plugin: kubevirt.core.kubevirt - connections: - - namespaces: - - testing - network_name: bridge-network +# Use default ~/.kube/config and return virtual machines from namespace testing connected to network bridge-network +- plugin: kubevirt.core.kubevirt + namespaces: + - testing + network_name: bridge-network -- name: Use default ~/.kube/config and return virtual machines from namespace testing with label app=test - plugin: kubevirt.core.kubevirt - connections: - - namespaces: - - testing - label_selector: app=test +# Use default ~/.kube/config and return virtual machines from namespace testing with label app=test +- plugin: kubevirt.core.kubevirt + namespaces: + - testing + label_selector: app=test -- name: Use a custom config file and a specific context - plugin: kubevirt.core.kubevirt - connections: - - kubeconfig: /path/to/config - context: 'awx/192-168-64-4:8443/developer' +# Use a custom config file and a specific context +- plugin: kubevirt.core.kubevirt + kubeconfig: /path/to/config + context: 'awx/192-168-64-4:8443/developer' """ -from dataclasses import dataclass +from dataclasses import dataclass, InitVar from json import loads from re import compile as re_compile from typing import ( @@ -246,21 +193,58 @@ class InventoryOptions: base_domain: Optional[str] = None append_base_domain: Optional[bool] = None host_format: Optional[str] = None + config_data: InitVar[Optional[Dict]] = None - def __post_init__(self): - # Set defaults in __post_init__ to allow instatiating class with None values - if self.api_version is None: - self.api_version = "kubevirt.io/v1" - if self.kube_secondary_dns is None: - self.kube_secondary_dns = False - if self.use_service is None: - self.use_service = True - if self.create_groups is None: - self.create_groups = False - if self.append_base_domain is None: - self.append_base_domain = False - if self.host_format is None: - self.host_format = "{namespace}-{name}" + def __post_init__(self, config_data: Optional[Dict]) -> None: + if not config_data or not isinstance(config_data, dict): + config_data = {} + + # Copy values from config_data and set defaults for keys not present + self.api_version = ( + self.api_version + if self.api_version is not None + else config_data.get("api_version", "kubevirt.io/v1") + ) + self.label_selector = ( + self.label_selector + if self.label_selector is not None + else config_data.get("label_selector") + ) + self.network_name = ( + self.network_name + if self.network_name is not None + else config_data.get("network_name", config_data.get("interface_name")) + ) + self.kube_secondary_dns = ( + self.kube_secondary_dns + if self.kube_secondary_dns is not None + else config_data.get("kube_secondary_dns", False) + ) + self.use_service = ( + self.use_service + if self.use_service is not None + else config_data.get("use_service", True) + ) + self.create_groups = ( + self.create_groups + if self.create_groups is not None + else config_data.get("create_groups", False) + ) + self.base_domain = ( + self.base_domain + if self.base_domain is not None + else config_data.get("base_domain") + ) + self.append_base_domain = ( + self.append_base_domain + if self.append_base_domain is not None + else config_data.get("append_base_domain", False) + ) + self.host_format = ( + self.host_format + if self.host_format is not None + else config_data.get("host_format", "{namespace}-{name}") + ) class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): @@ -378,7 +362,6 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): def __init__(self) -> None: super().__init__() - self.host_format = None def verify_file(self, path: str) -> None: """ @@ -390,20 +373,15 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): def parse(self, inventory: Any, loader: Any, path: str, cache: bool = True) -> None: """ - parse runs basic setup of the inventory. + parse is the main entry point of the inventory. + It checks for availability of the Kubernetes Python client, + gets the configuration and runs fetch_objects or + if there is a cache it is used instead. """ super().parse(inventory, loader, path) cache_key = self._get_cache_prefix(path) config_data = self._read_config_data(path) - self.host_format = config_data.get("host_format") - self.setup(config_data, cache, cache_key) - def setup(self, config_data: Dict, cache: bool, cache_key: str) -> None: - """ - setup checks for availability of the Kubernetes Python client, - gets the configured connections and runs fetch_objects on them. - If there is a cache it is returned instead. - """ if not HAS_K8S_MODULE_HELPER: raise KubeVirtInventoryException( "This module requires the Kubernetes Python client. " @@ -418,61 +396,66 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): pass if not source_data: - self.fetch_objects(config_data.get("connections")) + self.fetch_objects(config_data) - def fetch_objects(self, connections: Optional[List[Dict]]) -> None: + def fetch_objects(self, config_data: Dict) -> None: """ - fetch_objects populates the inventory with every configured connection. + fetch_objects populates the inventory with the specified parameters. """ - if connections: + if not config_data or not isinstance(config_data, dict): + config_data = {} + + self.connections_compatibility(config_data) + client = get_api_client(**config_data) + name = config_data.get( + "name", self.get_default_host_name(client.configuration.host) + ) + namespaces = ( + config_data["namespaces"] + if config_data.get("namespaces") + else self.get_available_namespaces(client) + ) + opts = InventoryOptions(config_data=config_data) + if opts.base_domain is None: + opts.base_domain = self.get_cluster_domain(client) + for namespace in namespaces: + self.populate_inventory_from_namespace(client, name, namespace, opts) + + def connections_compatibility(self, config_data: Dict) -> None: + collection_name = "kubevirt.core" + + if (connections := config_data.get("connections")) is None: + return + + self.display.deprecated( + msg="The 'connections' parameter is deprecated and now supports only a single list entry.", + version="2.0.0", + collection_name=collection_name, + ) + + if not isinstance(connections, list): + raise KubeVirtInventoryException("Expecting connections to be a list.") + + if len(connections) == 1: + if not isinstance(connections[0], dict): + raise KubeVirtInventoryException( + "Expecting connection to be a dictionary." + ) + # Copy the single connections entry into the top level + for k, v in connections[0].items(): + config_data[k] = v self.display.deprecated( - msg="The 'connections' parameter is deprecated and starting with version 2.0.0 of kubevirt.core supports only a single entry.", + msg="Move all of your connection parameters to the configuration top level.", version="3.0.0", - collection_name="kubevirt.core", + collection_name=collection_name, ) - - if not isinstance(connections, list): - raise KubeVirtInventoryException("Expecting connections to be a list.") - - for connection in connections: - if not isinstance(connection, dict): - raise KubeVirtInventoryException( - "Expecting connection to be a dictionary." - ) - client = 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) - - opts = InventoryOptions( - connection.get("api_version"), - connection.get("label_selector"), - connection.get("network_name", connection.get("interface_name")), - connection.get("kube_secondary_dns"), - connection.get("use_service"), - connection.get("create_groups"), - connection.get("base_domain", self.get_cluster_domain(client)), - connection.get("append_base_domain"), - self.host_format, - ) - for namespace in namespaces: - self.populate_inventory_from_namespace( - client, name, namespace, opts - ) - else: - client = get_api_client() - name = self.get_default_host_name(client.configuration.host) - namespaces = self.get_available_namespaces(client) - opts = InventoryOptions( - host_format=self.host_format, - base_domain=self.get_cluster_domain(client), + elif len(connections) > 1: + self.display.deprecated( + msg="Split your connections into multiple configuration files.", + version="2.0.0", + collection_name=collection_name, + removed=True, ) - for namespace in namespaces: - self.populate_inventory_from_namespace(client, name, namespace, opts) def get_cluster_domain(self, client: K8SClient) -> Optional[str]: """ diff --git a/tests/integration/targets/inventory_kubevirt/test.kubevirt.yml b/tests/integration/targets/inventory_kubevirt/test.kubevirt.yml index 717c255..976bbb0 100644 --- a/tests/integration/targets/inventory_kubevirt/test.kubevirt.yml +++ b/tests/integration/targets/inventory_kubevirt/test.kubevirt.yml @@ -1,7 +1,6 @@ --- plugin: kubevirt.core.kubevirt -connections: - - name: test - namespaces: - - default - create_groups: true +name: test +namespaces: + - default +create_groups: true diff --git a/tests/integration/targets/inventory_kubevirt/test.label.kubevirt.yml b/tests/integration/targets/inventory_kubevirt/test.label.kubevirt.yml index 3917365..6509b58 100644 --- a/tests/integration/targets/inventory_kubevirt/test.label.kubevirt.yml +++ b/tests/integration/targets/inventory_kubevirt/test.label.kubevirt.yml @@ -1,7 +1,6 @@ --- plugin: kubevirt.core.kubevirt -connections: - - namespaces: - - default - create_groups: true - label_selector: app=test +namespaces: + - default +create_groups: true +label_selector: app=test diff --git a/tests/integration/targets/inventory_kubevirt/test.net.kubevirt.yml b/tests/integration/targets/inventory_kubevirt/test.net.kubevirt.yml index a8591b2..0e127bb 100644 --- a/tests/integration/targets/inventory_kubevirt/test.net.kubevirt.yml +++ b/tests/integration/targets/inventory_kubevirt/test.net.kubevirt.yml @@ -1,7 +1,6 @@ --- plugin: kubevirt.core.kubevirt -connections: - - namespaces: - - default - create_groups: true - network_name: bridge-network +namespaces: + - default +create_groups: true +network_name: bridge-network diff --git a/tests/integration/targets/kubevirt_vm/test.kubevirt.yml b/tests/integration/targets/kubevirt_vm/test.kubevirt.yml index 9d8c177..a3bc026 100644 --- a/tests/integration/targets/kubevirt_vm/test.kubevirt.yml +++ b/tests/integration/targets/kubevirt_vm/test.kubevirt.yml @@ -1,6 +1,5 @@ --- plugin: kubevirt.core.kubevirt -connections: - - namespaces: - - default - network_name: bridge-network +namespaces: + - default +network_name: bridge-network diff --git a/tests/unit/plugins/inventory/test_kubevirt.py b/tests/unit/plugins/inventory/test_kubevirt.py index 477abf6..ed28731 100644 --- a/tests/unit/plugins/inventory/test_kubevirt.py +++ b/tests/unit/plugins/inventory/test_kubevirt.py @@ -206,29 +206,6 @@ def test_is_windows(guest_os_info, annotations, expected): assert InventoryModule.is_windows(guest_os_info, annotations) == expected -def test_parse(mocker, inventory): - path = "/testpath" - cache_prefix = "test-prefix" - host_format = "test-format" - config_data = {"host_format": host_format} - cache = True - - get_cache_prefix = mocker.patch.object( - inventory, "_get_cache_prefix", return_value=cache_prefix - ) - read_config_data = mocker.patch.object( - inventory, "_read_config_data", return_value=config_data - ) - setup = mocker.patch.object(inventory, "setup") - - inventory.parse(None, None, path, cache) - - get_cache_prefix.assert_called_once_with(path) - read_config_data.assert_called_once_with(path) - setup.assert_called_once_with(config_data, cache, cache_prefix) - assert inventory.host_format == host_format - - def test_get_cluster_domain(inventory, client): assert inventory.get_cluster_domain(client) == DEFAULT_BASE_DOMAIN diff --git a/tests/unit/plugins/inventory/test_kubevirt_connections_compatibility.py b/tests/unit/plugins/inventory/test_kubevirt_connections_compatibility.py new file mode 100644 index 0000000..64f4818 --- /dev/null +++ b/tests/unit/plugins/inventory/test_kubevirt_connections_compatibility.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 Red Hat, Inc. +# Apache License 2.0 (see LICENSE or http://www.apache.org/licenses/LICENSE-2.0) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import pytest + +from ansible.errors import AnsibleError + +from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import ( + KubeVirtInventoryException, +) + + +def test_config_data_without_connections_ignored(inventory): + config_data = { + "name": "connection-1", + "namespaces": ["default"], + "network_name": "bridge-network", + "label_selector": "app=test", + } + + inventory.connections_compatibility(config_data) + + assert config_data["name"] == "connection-1" + assert config_data["namespaces"] == ["default"] + assert config_data["network_name"] == "bridge-network" + assert config_data["label_selector"] == "app=test" + + +def test_single_connection_supported(inventory): + config_data = { + "connections": [ + { + "name": "connection-1", + "namespaces": ["default"], + "network_name": "bridge-network", + "label_selector": "app=test", + } + ], + "namespaces": ["something"], + "network_name": "some-network", + "label_selector": "app=something", + } + + inventory.connections_compatibility(config_data) + + assert config_data["name"] == "connection-1" + assert config_data["namespaces"] == ["default"] + assert config_data["network_name"] == "bridge-network" + assert config_data["label_selector"] == "app=test" + + +def test_multiple_connections_not_supported(inventory): + with pytest.raises( + AnsibleError, match="Split your connections into multiple configuration files." + ): + inventory.connections_compatibility( + { + "connections": [ + { + "name": "connection-1", + }, + { + "name": "connection-2", + }, + ], + } + ) + + +@pytest.mark.parametrize( + "config_data,expected", + [ + ({"connections": "test"}, "Expecting connections to be a list."), + ( + {"connections": [["test", "test"]]}, + "Expecting connection to be a dictionary.", + ), + ], +) +def test_connections_exceptions(inventory, config_data, expected): + with pytest.raises(KubeVirtInventoryException, match=expected): + inventory.connections_compatibility(config_data) diff --git a/tests/unit/plugins/inventory/test_kubevirt_fetch_objects.py b/tests/unit/plugins/inventory/test_kubevirt_fetch_objects.py index 14ed667..485c41b 100644 --- a/tests/unit/plugins/inventory/test_kubevirt_fetch_objects.py +++ b/tests/unit/plugins/inventory/test_kubevirt_fetch_objects.py @@ -11,7 +11,6 @@ import pytest from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import ( InventoryOptions, - KubeVirtInventoryException, ) from ansible_collections.kubevirt.core.tests.unit.plugins.inventory.constants import ( @@ -22,135 +21,97 @@ from ansible_collections.kubevirt.core.plugins.inventory import kubevirt @pytest.mark.parametrize( - "connections,expected", + "config_data,expected", [ ( None, - [ - { - "name": "default-hostname", - "namespace": DEFAULT_NAMESPACE, - "opts": InventoryOptions(), - }, - ], + { + "name": "default-hostname", + "namespaces": [DEFAULT_NAMESPACE], + "opts": InventoryOptions(), + }, ), ( - [ - { - "name": "test", - }, - ], - [ - { - "name": "test", - "namespace": DEFAULT_NAMESPACE, - "opts": InventoryOptions(), - }, - ], + { + "name": "test", + }, + { + "name": "test", + "namespaces": [DEFAULT_NAMESPACE], + "opts": InventoryOptions(), + }, ), ( - [ - {"name": "test", "namespaces": ["test"]}, - ], - [ - {"name": "test", "namespace": "test", "opts": InventoryOptions()}, - ], + {"name": "test", "namespaces": ["test"]}, + {"name": "test", "namespaces": ["test"], "opts": InventoryOptions()}, ), ( - [ - { - "name": "test", - "namespaces": ["test"], - "use_service": True, - "create_groups": True, - "append_base_domain": True, - "base_domain": "test-domain", - }, - ], - [ - { - "name": "test", - "namespace": "test", - "opts": InventoryOptions( - use_service=True, - create_groups=True, - append_base_domain=True, - base_domain="test-domain", - ), - }, - ], + { + "name": "test", + "namespaces": ["test"], + "use_service": True, + "create_groups": True, + "append_base_domain": True, + "base_domain": "test-domain", + }, + { + "name": "test", + "namespaces": ["test"], + "opts": InventoryOptions( + use_service=True, + create_groups=True, + append_base_domain=True, + base_domain="test-domain", + ), + }, ), ( - [ - { - "name": "test", - "namespaces": ["test"], - "use_service": True, - "create_groups": True, - "append_base_domain": True, - "base_domain": "test-domain", - "network_name": "test-network", - }, - ], - [ - { - "name": "test", - "namespace": "test", - "opts": InventoryOptions( - use_service=True, - create_groups=True, - append_base_domain=True, - base_domain="test-domain", - network_name="test-network", - ), - }, - ], + { + "name": "test", + "namespaces": ["test"], + "use_service": True, + "create_groups": True, + "append_base_domain": True, + "base_domain": "test-domain", + "network_name": "test-network", + }, + { + "name": "test", + "namespaces": ["test"], + "opts": InventoryOptions( + use_service=True, + create_groups=True, + append_base_domain=True, + base_domain="test-domain", + network_name="test-network", + ), + }, ), ( - [ - { - "name": "test", - "namespaces": ["test"], - "use_service": True, - "create_groups": True, - "append_base_domain": True, - "base_domain": "test-domain", - "interface_name": "test-interface", - }, - ], - [ - { - "name": "test", - "namespace": "test", - "opts": InventoryOptions( - use_service=True, - create_groups=True, - append_base_domain=True, - base_domain="test-domain", - network_name="test-interface", - ), - }, - ], - ), - ( - [ - { - "name": "test", - }, - {"name": "test", "namespaces": ["test"]}, - ], - [ - { - "name": "test", - "namespace": DEFAULT_NAMESPACE, - "opts": InventoryOptions(), - }, - {"name": "test", "namespace": "test", "opts": InventoryOptions()}, - ], + { + "name": "test", + "namespaces": ["test"], + "use_service": True, + "create_groups": True, + "append_base_domain": True, + "base_domain": "test-domain", + "interface_name": "test-interface", + }, + { + "name": "test", + "namespaces": ["test"], + "opts": InventoryOptions( + use_service=True, + create_groups=True, + append_base_domain=True, + base_domain="test-domain", + network_name="test-interface", + ), + }, ), ], ) -def test_fetch_objects(mocker, inventory, connections, expected): +def test_fetch_objects(mocker, inventory, config_data, expected): mocker.patch.object(kubevirt, "get_api_client") mocker.patch.object( inventory, "get_default_host_name", return_value="default-hostname" @@ -158,8 +119,7 @@ def test_fetch_objects(mocker, inventory, connections, expected): cluster_domain = "test.com" mocker.patch.object(inventory, "get_cluster_domain", return_value=cluster_domain) - for e in expected: - e["opts"].base_domain = e["opts"].base_domain or cluster_domain + expected["opts"].base_domain = expected["opts"].base_domain or cluster_domain get_available_namespaces = mocker.patch.object( inventory, "get_available_namespaces", return_value=[DEFAULT_NAMESPACE] @@ -168,26 +128,16 @@ def test_fetch_objects(mocker, inventory, connections, expected): inventory, "populate_inventory_from_namespace" ) - inventory.fetch_objects(connections) + inventory.fetch_objects(config_data) + + if config_data and "namespaces" in config_data: + get_available_namespaces.assert_not_called() + else: + get_available_namespaces.assert_called() - get_available_namespaces.assert_has_calls( - [mocker.call(mocker.ANY) for c in connections or [{}] if "namespaces" not in c] - ) populate_inventory_from_namespace.assert_has_calls( [ - mocker.call(mocker.ANY, e["name"], e["namespace"], e["opts"]) - for e in expected + mocker.call(mocker.ANY, expected["name"], namespace, expected["opts"]) + for namespace in expected["namespaces"] ] ) - - -@pytest.mark.parametrize( - "connections,expected", - [ - ("test", "Expecting connections to be a list."), - (["test", "test"], "Expecting connection to be a dictionary."), - ], -) -def test_fetch_objects_exceptions(inventory, connections, expected): - with pytest.raises(KubeVirtInventoryException, match=expected): - inventory.fetch_objects(connections) diff --git a/tests/unit/plugins/inventory/test_kubevirt_setup.py b/tests/unit/plugins/inventory/test_kubevirt_parse.py similarity index 58% rename from tests/unit/plugins/inventory/test_kubevirt_setup.py rename to tests/unit/plugins/inventory/test_kubevirt_parse.py index 6aa6edd..634752b 100644 --- a/tests/unit/plugins/inventory/test_kubevirt_setup.py +++ b/tests/unit/plugins/inventory/test_kubevirt_parse.py @@ -24,20 +24,28 @@ from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import ( False, ], ) -def test_cache_is_used(mocker, inventory, cache): - connections = [{"test-conn": {}}] - config_data = {"connections": connections} - cache_key = "test-prefix" +def test_parse(mocker, inventory, cache): + path = "/testpath" + cache_prefix = "test-prefix" + config_data = {"host_format": "test-format"} - mocker.patch.dict(inventory._cache, {cache_key: {"test-key": "test-value"}}) + mocker.patch.dict(inventory._cache, {cache_prefix: {"test-key": "test-value"}}) + get_cache_prefix = mocker.patch.object( + inventory, "_get_cache_prefix", return_value=cache_prefix + ) + read_config_data = mocker.patch.object( + inventory, "_read_config_data", return_value=config_data + ) fetch_objects = mocker.patch.object(inventory, "fetch_objects") - inventory.setup(config_data, cache, cache_key) + inventory.parse(None, None, path, cache) + get_cache_prefix.assert_called_once_with(path) + read_config_data.assert_called_once_with(path) if cache: fetch_objects.assert_not_called() else: - fetch_objects.assert_called_once_with(connections) + fetch_objects.assert_called_once_with(config_data) @pytest.mark.parametrize( @@ -49,15 +57,17 @@ def test_cache_is_used(mocker, inventory, cache): ) def test_k8s_client_missing(mocker, inventory, present): mocker.patch.object(kubevirt, "HAS_K8S_MODULE_HELPER", present) + mocker.patch.object(inventory, "_get_cache_prefix") + mocker.patch.object(inventory, "_read_config_data") fetch_objects = mocker.patch.object(inventory, "fetch_objects") if present: - inventory.setup({}, False, "test") + inventory.parse(None, None, "", False) fetch_objects.assert_called_once() else: with pytest.raises( KubeVirtInventoryException, match="This module requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: None", ): - inventory.setup({}, False, "test") + inventory.parse(None, None, "", False) fetch_objects.assert_not_called()