mirror of
https://github.com/kubevirt/kubevirt.core.git
synced 2026-07-28 18:34:36 +00:00
Merge pull request #117 from 0xFelix/drop-connections
cleanup(inventory): Drop support for multiple connections
This commit is contained in:
@@ -12,6 +12,8 @@ rules:
|
|||||||
- /changelogs/changelog.yaml
|
- /changelogs/changelog.yaml
|
||||||
|
|
||||||
ignore:
|
ignore:
|
||||||
|
- .tox/
|
||||||
|
- .venv/
|
||||||
- /tests/integration/targets/inventory_kubevirt/all.yml
|
- /tests/integration/targets/inventory_kubevirt/all.yml
|
||||||
- /tests/integration/targets/inventory_kubevirt/label.yml
|
- /tests/integration/targets/inventory_kubevirt/label.yml
|
||||||
- /tests/integration/targets/inventory_kubevirt/net.yml
|
- /tests/integration/targets/inventory_kubevirt/net.yml
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
plugin: kubevirt.core.kubevirt
|
plugin: kubevirt.core.kubevirt
|
||||||
connections:
|
namespaces:
|
||||||
- namespaces:
|
- default
|
||||||
- default
|
network_name: bridge-network
|
||||||
network_name: bridge-network
|
label_selector: app=test
|
||||||
label_selector: app=test
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
plugin: kubevirt.core.kubevirt
|
plugin: kubevirt.core.kubevirt
|
||||||
connections:
|
namespaces:
|
||||||
- namespaces:
|
- default
|
||||||
- default
|
network_name: bridge-network
|
||||||
network_name: bridge-network
|
kube_secondary_dns: true
|
||||||
kube_secondary_dns: true
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
plugin: kubevirt.core.kubevirt
|
plugin: kubevirt.core.kubevirt
|
||||||
connections:
|
namespaces:
|
||||||
- namespaces:
|
- default
|
||||||
- default
|
use_service: true
|
||||||
use_service: true
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ options:
|
|||||||
configuration file from I(~/.kube/config).
|
configuration file from I(~/.kube/config).
|
||||||
- Can also be specified via E(K8S_AUTH_KUBECONFIG) environment variable.
|
- Can also be specified via E(K8S_AUTH_KUBECONFIG) environment variable.
|
||||||
- Multiple Kubernetes config file can be provided using separator C(;) for Windows platform or C(:) for others platforms.
|
- Multiple Kubernetes config file can be provided using separator C(;) for Windows platform or C(:) for others platforms.
|
||||||
- The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0. Added in version 2.2.0.
|
- The kubernetes configuration can be provided as dictionary. This feature requires a python kubernetes client version >= 17.17.0.
|
||||||
type: raw
|
type: raw
|
||||||
context:
|
context:
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ author:
|
|||||||
|
|
||||||
description:
|
description:
|
||||||
- Fetch virtual machines from one or more namespaces with an optional label selector.
|
- Fetch virtual machines from one or more namespaces with an optional label selector.
|
||||||
- Groups by cluster name, namespace and labels.
|
- Groups by cluster name, namespaces and labels.
|
||||||
- Uses the M(kubernetes.core.kubectl) connection plugin to access the Kubernetes cluster.
|
|
||||||
- Uses V(*.kubevirt.[yml|yaml]) YAML configuration file to set parameter values.
|
- 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:
|
extends_documentation_fragment:
|
||||||
|
- kubevirt.core.kubevirt_auth_options
|
||||||
- inventory_cache
|
- inventory_cache
|
||||||
- constructed
|
- constructed
|
||||||
|
|
||||||
@@ -34,108 +36,57 @@ options:
|
|||||||
description:
|
description:
|
||||||
- 'Specify the format of the host in the inventory group. Available specifiers: V(name), V(namespace) and V(uid).'
|
- 'Specify the format of the host in the inventory group. Available specifiers: V(name), V(namespace) and V(uid).'
|
||||||
default: "{namespace}-{name}"
|
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:
|
connections:
|
||||||
description:
|
description:
|
||||||
- Optional list of cluster connection settings. If no connections are provided, the default
|
- Optional list of cluster connection settings.
|
||||||
I(~/.kube/config) and active context will be used, and objects will be returned for all namespaces
|
|
||||||
the active user is authorized to access.
|
|
||||||
- This parameter is deprecated. Split your connections into multiple configuration files and move
|
- This parameter is deprecated. Split your connections into multiple configuration files and move
|
||||||
parameters of each connection to the configuration top level.
|
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).
|
- 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:
|
requirements:
|
||||||
- "python >= 3.9"
|
- "python >= 3.9"
|
||||||
@@ -146,35 +97,31 @@ requirements:
|
|||||||
EXAMPLES = """
|
EXAMPLES = """
|
||||||
# Filename must end with kubevirt.[yml|yaml]
|
# Filename must end with kubevirt.[yml|yaml]
|
||||||
|
|
||||||
- name: Authenticate with token and return all virtual machines from all accessible namespaces
|
# Authenticate with token and return all virtual machines from all accessible namespaces
|
||||||
plugin: kubevirt.core.kubevirt
|
- plugin: kubevirt.core.kubevirt
|
||||||
connections:
|
host: https://192.168.64.4:8443
|
||||||
- host: https://192.168.64.4:8443
|
api_key: xxxxxxxxxxxxxxxx
|
||||||
api_key: xxxxxxxxxxxxxxxx
|
validate_certs: false
|
||||||
validate_certs: false
|
|
||||||
|
|
||||||
- name: Use default ~/.kube/config and return virtual machines from namespace testing connected to network bridge-network
|
# Use default ~/.kube/config and return virtual machines from namespace testing connected to network bridge-network
|
||||||
plugin: kubevirt.core.kubevirt
|
- plugin: kubevirt.core.kubevirt
|
||||||
connections:
|
namespaces:
|
||||||
- namespaces:
|
- testing
|
||||||
- testing
|
network_name: bridge-network
|
||||||
network_name: bridge-network
|
|
||||||
|
|
||||||
- name: Use default ~/.kube/config and return virtual machines from namespace testing with label app=test
|
# Use default ~/.kube/config and return virtual machines from namespace testing with label app=test
|
||||||
plugin: kubevirt.core.kubevirt
|
- plugin: kubevirt.core.kubevirt
|
||||||
connections:
|
namespaces:
|
||||||
- namespaces:
|
- testing
|
||||||
- testing
|
label_selector: app=test
|
||||||
label_selector: app=test
|
|
||||||
|
|
||||||
- name: Use a custom config file and a specific context
|
# Use a custom config file and a specific context
|
||||||
plugin: kubevirt.core.kubevirt
|
- plugin: kubevirt.core.kubevirt
|
||||||
connections:
|
kubeconfig: /path/to/config
|
||||||
- kubeconfig: /path/to/config
|
context: 'awx/192-168-64-4:8443/developer'
|
||||||
context: 'awx/192-168-64-4:8443/developer'
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, InitVar
|
||||||
from json import loads
|
from json import loads
|
||||||
from re import compile as re_compile
|
from re import compile as re_compile
|
||||||
from typing import (
|
from typing import (
|
||||||
@@ -246,21 +193,58 @@ class InventoryOptions:
|
|||||||
base_domain: Optional[str] = None
|
base_domain: Optional[str] = None
|
||||||
append_base_domain: Optional[bool] = None
|
append_base_domain: Optional[bool] = None
|
||||||
host_format: Optional[str] = None
|
host_format: Optional[str] = None
|
||||||
|
config_data: InitVar[Optional[Dict]] = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self, config_data: Optional[Dict]) -> None:
|
||||||
# Set defaults in __post_init__ to allow instatiating class with None values
|
if not config_data or not isinstance(config_data, dict):
|
||||||
if self.api_version is None:
|
config_data = {}
|
||||||
self.api_version = "kubevirt.io/v1"
|
|
||||||
if self.kube_secondary_dns is None:
|
# Copy values from config_data and set defaults for keys not present
|
||||||
self.kube_secondary_dns = False
|
self.api_version = (
|
||||||
if self.use_service is None:
|
self.api_version
|
||||||
self.use_service = True
|
if self.api_version is not None
|
||||||
if self.create_groups is None:
|
else config_data.get("api_version", "kubevirt.io/v1")
|
||||||
self.create_groups = False
|
)
|
||||||
if self.append_base_domain is None:
|
self.label_selector = (
|
||||||
self.append_base_domain = False
|
self.label_selector
|
||||||
if self.host_format is None:
|
if self.label_selector is not None
|
||||||
self.host_format = "{namespace}-{name}"
|
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):
|
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||||
@@ -378,7 +362,6 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.host_format = None
|
|
||||||
|
|
||||||
def verify_file(self, path: str) -> 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:
|
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)
|
super().parse(inventory, loader, path)
|
||||||
cache_key = self._get_cache_prefix(path)
|
cache_key = self._get_cache_prefix(path)
|
||||||
config_data = self._read_config_data(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:
|
if not HAS_K8S_MODULE_HELPER:
|
||||||
raise KubeVirtInventoryException(
|
raise KubeVirtInventoryException(
|
||||||
"This module requires the Kubernetes Python client. "
|
"This module requires the Kubernetes Python client. "
|
||||||
@@ -418,61 +396,66 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if not source_data:
|
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(
|
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",
|
version="3.0.0",
|
||||||
collection_name="kubevirt.core",
|
collection_name=collection_name,
|
||||||
)
|
)
|
||||||
|
elif len(connections) > 1:
|
||||||
if not isinstance(connections, list):
|
self.display.deprecated(
|
||||||
raise KubeVirtInventoryException("Expecting connections to be a list.")
|
msg="Split your connections into multiple configuration files.",
|
||||||
|
version="2.0.0",
|
||||||
for connection in connections:
|
collection_name=collection_name,
|
||||||
if not isinstance(connection, dict):
|
removed=True,
|
||||||
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),
|
|
||||||
)
|
)
|
||||||
for namespace in namespaces:
|
|
||||||
self.populate_inventory_from_namespace(client, name, namespace, opts)
|
|
||||||
|
|
||||||
def get_cluster_domain(self, client: K8SClient) -> Optional[str]:
|
def get_cluster_domain(self, client: K8SClient) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
plugin: kubevirt.core.kubevirt
|
plugin: kubevirt.core.kubevirt
|
||||||
connections:
|
name: test
|
||||||
- name: test
|
namespaces:
|
||||||
namespaces:
|
- default
|
||||||
- default
|
create_groups: true
|
||||||
create_groups: true
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
plugin: kubevirt.core.kubevirt
|
plugin: kubevirt.core.kubevirt
|
||||||
connections:
|
namespaces:
|
||||||
- namespaces:
|
- default
|
||||||
- default
|
create_groups: true
|
||||||
create_groups: true
|
label_selector: app=test
|
||||||
label_selector: app=test
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
---
|
---
|
||||||
plugin: kubevirt.core.kubevirt
|
plugin: kubevirt.core.kubevirt
|
||||||
connections:
|
namespaces:
|
||||||
- namespaces:
|
- default
|
||||||
- default
|
create_groups: true
|
||||||
create_groups: true
|
network_name: bridge-network
|
||||||
network_name: bridge-network
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
plugin: kubevirt.core.kubevirt
|
plugin: kubevirt.core.kubevirt
|
||||||
connections:
|
namespaces:
|
||||||
- namespaces:
|
- default
|
||||||
- default
|
network_name: bridge-network
|
||||||
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
|
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):
|
def test_get_cluster_domain(inventory, client):
|
||||||
assert inventory.get_cluster_domain(client) == DEFAULT_BASE_DOMAIN
|
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 (
|
from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import (
|
||||||
InventoryOptions,
|
InventoryOptions,
|
||||||
KubeVirtInventoryException,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.kubevirt.core.tests.unit.plugins.inventory.constants import (
|
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(
|
@pytest.mark.parametrize(
|
||||||
"connections,expected",
|
"config_data,expected",
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
None,
|
None,
|
||||||
[
|
{
|
||||||
{
|
"name": "default-hostname",
|
||||||
"name": "default-hostname",
|
"namespaces": [DEFAULT_NAMESPACE],
|
||||||
"namespace": DEFAULT_NAMESPACE,
|
"opts": InventoryOptions(),
|
||||||
"opts": InventoryOptions(),
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
[
|
{
|
||||||
{
|
"name": "test",
|
||||||
"name": "test",
|
},
|
||||||
},
|
{
|
||||||
],
|
"name": "test",
|
||||||
[
|
"namespaces": [DEFAULT_NAMESPACE],
|
||||||
{
|
"opts": InventoryOptions(),
|
||||||
"name": "test",
|
},
|
||||||
"namespace": DEFAULT_NAMESPACE,
|
|
||||||
"opts": InventoryOptions(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
[
|
{"name": "test", "namespaces": ["test"]},
|
||||||
{"name": "test", "namespaces": ["test"]},
|
{"name": "test", "namespaces": ["test"], "opts": InventoryOptions()},
|
||||||
],
|
|
||||||
[
|
|
||||||
{"name": "test", "namespace": "test", "opts": InventoryOptions()},
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
[
|
{
|
||||||
{
|
"name": "test",
|
||||||
"name": "test",
|
"namespaces": ["test"],
|
||||||
"namespaces": ["test"],
|
"use_service": True,
|
||||||
"use_service": True,
|
"create_groups": True,
|
||||||
"create_groups": True,
|
"append_base_domain": True,
|
||||||
"append_base_domain": True,
|
"base_domain": "test-domain",
|
||||||
"base_domain": "test-domain",
|
},
|
||||||
},
|
{
|
||||||
],
|
"name": "test",
|
||||||
[
|
"namespaces": ["test"],
|
||||||
{
|
"opts": InventoryOptions(
|
||||||
"name": "test",
|
use_service=True,
|
||||||
"namespace": "test",
|
create_groups=True,
|
||||||
"opts": InventoryOptions(
|
append_base_domain=True,
|
||||||
use_service=True,
|
base_domain="test-domain",
|
||||||
create_groups=True,
|
),
|
||||||
append_base_domain=True,
|
},
|
||||||
base_domain="test-domain",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
[
|
{
|
||||||
{
|
"name": "test",
|
||||||
"name": "test",
|
"namespaces": ["test"],
|
||||||
"namespaces": ["test"],
|
"use_service": True,
|
||||||
"use_service": True,
|
"create_groups": True,
|
||||||
"create_groups": True,
|
"append_base_domain": True,
|
||||||
"append_base_domain": True,
|
"base_domain": "test-domain",
|
||||||
"base_domain": "test-domain",
|
"network_name": "test-network",
|
||||||
"network_name": "test-network",
|
},
|
||||||
},
|
{
|
||||||
],
|
"name": "test",
|
||||||
[
|
"namespaces": ["test"],
|
||||||
{
|
"opts": InventoryOptions(
|
||||||
"name": "test",
|
use_service=True,
|
||||||
"namespace": "test",
|
create_groups=True,
|
||||||
"opts": InventoryOptions(
|
append_base_domain=True,
|
||||||
use_service=True,
|
base_domain="test-domain",
|
||||||
create_groups=True,
|
network_name="test-network",
|
||||||
append_base_domain=True,
|
),
|
||||||
base_domain="test-domain",
|
},
|
||||||
network_name="test-network",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
[
|
{
|
||||||
{
|
"name": "test",
|
||||||
"name": "test",
|
"namespaces": ["test"],
|
||||||
"namespaces": ["test"],
|
"use_service": True,
|
||||||
"use_service": True,
|
"create_groups": True,
|
||||||
"create_groups": True,
|
"append_base_domain": True,
|
||||||
"append_base_domain": True,
|
"base_domain": "test-domain",
|
||||||
"base_domain": "test-domain",
|
"interface_name": "test-interface",
|
||||||
"interface_name": "test-interface",
|
},
|
||||||
},
|
{
|
||||||
],
|
"name": "test",
|
||||||
[
|
"namespaces": ["test"],
|
||||||
{
|
"opts": InventoryOptions(
|
||||||
"name": "test",
|
use_service=True,
|
||||||
"namespace": "test",
|
create_groups=True,
|
||||||
"opts": InventoryOptions(
|
append_base_domain=True,
|
||||||
use_service=True,
|
base_domain="test-domain",
|
||||||
create_groups=True,
|
network_name="test-interface",
|
||||||
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()},
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
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(kubevirt, "get_api_client")
|
||||||
mocker.patch.object(
|
mocker.patch.object(
|
||||||
inventory, "get_default_host_name", return_value="default-hostname"
|
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"
|
cluster_domain = "test.com"
|
||||||
mocker.patch.object(inventory, "get_cluster_domain", return_value=cluster_domain)
|
mocker.patch.object(inventory, "get_cluster_domain", return_value=cluster_domain)
|
||||||
for e in expected:
|
expected["opts"].base_domain = expected["opts"].base_domain or cluster_domain
|
||||||
e["opts"].base_domain = e["opts"].base_domain or cluster_domain
|
|
||||||
|
|
||||||
get_available_namespaces = mocker.patch.object(
|
get_available_namespaces = mocker.patch.object(
|
||||||
inventory, "get_available_namespaces", return_value=[DEFAULT_NAMESPACE]
|
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, "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(
|
populate_inventory_from_namespace.assert_has_calls(
|
||||||
[
|
[
|
||||||
mocker.call(mocker.ANY, e["name"], e["namespace"], e["opts"])
|
mocker.call(mocker.ANY, expected["name"], namespace, expected["opts"])
|
||||||
for e in expected
|
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,
|
False,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_cache_is_used(mocker, inventory, cache):
|
def test_parse(mocker, inventory, cache):
|
||||||
connections = [{"test-conn": {}}]
|
path = "/testpath"
|
||||||
config_data = {"connections": connections}
|
cache_prefix = "test-prefix"
|
||||||
cache_key = "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")
|
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:
|
if cache:
|
||||||
fetch_objects.assert_not_called()
|
fetch_objects.assert_not_called()
|
||||||
else:
|
else:
|
||||||
fetch_objects.assert_called_once_with(connections)
|
fetch_objects.assert_called_once_with(config_data)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@@ -49,15 +57,17 @@ def test_cache_is_used(mocker, inventory, cache):
|
|||||||
)
|
)
|
||||||
def test_k8s_client_missing(mocker, inventory, present):
|
def test_k8s_client_missing(mocker, inventory, present):
|
||||||
mocker.patch.object(kubevirt, "HAS_K8S_MODULE_HELPER", 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")
|
fetch_objects = mocker.patch.object(inventory, "fetch_objects")
|
||||||
|
|
||||||
if present:
|
if present:
|
||||||
inventory.setup({}, False, "test")
|
inventory.parse(None, None, "", False)
|
||||||
fetch_objects.assert_called_once()
|
fetch_objects.assert_called_once()
|
||||||
else:
|
else:
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
KubeVirtInventoryException,
|
KubeVirtInventoryException,
|
||||||
match="This module requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: None",
|
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()
|
fetch_objects.assert_not_called()
|
||||||
Reference in New Issue
Block a user