mirror of
https://github.com/kubevirt/kubevirt.core.git
synced 2026-03-26 19:03:16 +00:00
cleanup(inventory): Drop support for multiple connections
The support for connections to multiple clusters in the inventory plugin is dropped to better align with user expectations and how other inventories work. If inventories of multiple clusters are needed the inventory can be run multiple times with different configurations. This also helps to clean up the code and make it simpler. For now this adds a compatibility helper so that configurations with a single connection entry remain supported and a warning is emitted. Signed-off-by: Felix Matouschek <fmatouschek@redhat.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
plugin: kubevirt.core.kubevirt
|
||||
connections:
|
||||
- namespaces:
|
||||
- default
|
||||
use_service: true
|
||||
namespaces:
|
||||
- default
|
||||
use_service: true
|
||||
|
||||
@@ -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]:
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
plugin: kubevirt.core.kubevirt
|
||||
connections:
|
||||
- name: test
|
||||
namespaces:
|
||||
- default
|
||||
create_groups: true
|
||||
name: test
|
||||
namespaces:
|
||||
- default
|
||||
create_groups: true
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
plugin: kubevirt.core.kubevirt
|
||||
connections:
|
||||
- namespaces:
|
||||
- default
|
||||
network_name: bridge-network
|
||||
namespaces:
|
||||
- default
|
||||
network_name: bridge-network
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user