From 7524e74bb71716b2cc08e24b8bddb166358cdc03 Mon Sep 17 00:00:00 2001 From: Orion Poplawski Date: Sat, 26 Apr 2025 04:34:11 -0600 Subject: [PATCH] cobbler.inventory: Allow collecting fully rendered facts (#9975) Add facts_level option to cobbler inventory plugin, cleanpu Initialize connection in parse() Handle rendered system data in _get_systems() so it can be cached Signed-off-by: Orion Poplawski Co-authored-by: Tyler Phillippe --- .../9975-inventory-cobbler-as-rendered.yml | 2 + plugins/inventory/cobbler.py | 56 +++++++++++-------- 2 files changed, 36 insertions(+), 22 deletions(-) create mode 100644 changelogs/fragments/9975-inventory-cobbler-as-rendered.yml diff --git a/changelogs/fragments/9975-inventory-cobbler-as-rendered.yml b/changelogs/fragments/9975-inventory-cobbler-as-rendered.yml new file mode 100644 index 0000000000..d99dd0f27a --- /dev/null +++ b/changelogs/fragments/9975-inventory-cobbler-as-rendered.yml @@ -0,0 +1,2 @@ +minor_changes: + - cobbler inventory plugin - add ``facts_level`` option to allow requesting fully rendered variables for Cobbler systems (https://github.com/ansible-collections/community.general/issues/9419, https://github.com/ansible-collections/community.general/pull/9975). diff --git a/plugins/inventory/cobbler.py b/plugins/inventory/cobbler.py index 4546bf8d6c..a81055538a 100644 --- a/plugins/inventory/cobbler.py +++ b/plugins/inventory/cobbler.py @@ -94,16 +94,24 @@ DOCUMENTATION = ''' description: Prefix to apply to cobbler groups. default: cobbler_ want_facts: - description: Toggle, if V(true) the plugin will retrieve host facts from the server. + description: Toggle, if V(true) the plugin will retrieve all host facts from the server. type: boolean default: true want_ip_addresses: description: - - Toggle, if V(true) the plugin will add a C(cobbler_ipv4_addresses) and C(cobbleer_ipv6_addresses) dictionary to the defined O(group) mapping + - Toggle, if V(true) the plugin will add a C(cobbler_ipv4_addresses) and C(cobbler_ipv6_addresses) dictionary to the defined O(group) mapping interface DNS names to IP addresses. type: boolean default: true version_added: 7.1.0 + facts_level: + description: + - "Set to V(normal) to gather only system-level variables." + - "Set to V(as_rendered) to gather all variables as rolled up by Cobbler." + type: string + choices: [ 'normal', 'as_rendered' ] + default: normal + version_added: 10.7.0 ''' EXAMPLES = ''' @@ -142,7 +150,9 @@ class InventoryModule(BaseInventoryPlugin, Cacheable): def __init__(self): super(InventoryModule, self).__init__() self.cache_key = None - self.connection = None + + if not HAS_XMLRPC_CLIENT: + raise AnsibleError('Could not import xmlrpc client library') def verify_file(self, path): valid = False @@ -153,18 +163,6 @@ class InventoryModule(BaseInventoryPlugin, Cacheable): self.display.vvv('Skipping due to inventory source not ending in "cobbler.yaml" nor "cobbler.yml"') return valid - def _get_connection(self): - if not HAS_XMLRPC_CLIENT: - raise AnsibleError('Could not import xmlrpc client library') - - if self.connection is None: - self.display.vvvv(f'Connecting to {self.cobbler_url}\n') - self.connection = xmlrpc_client.Server(self.cobbler_url, allow_none=True) - self.token = None - if self.get_option('user') is not None: - self.token = self.connection.login(text_type(self.get_option('user')), text_type(self.get_option('password'))) - return self.connection - def _init_cache(self): if self.cache_key not in self._cache: self._cache[self.cache_key] = {} @@ -178,12 +176,11 @@ class InventoryModule(BaseInventoryPlugin, Cacheable): def _get_profiles(self): if not self.use_cache or 'profiles' not in self._cache.get(self.cache_key, {}): - c = self._get_connection() try: if self.token is not None: - data = c.get_profiles(self.token) + data = self.cobbler.get_profiles(self.token) else: - data = c.get_profiles() + data = self.cobbler.get_profiles() except (socket.gaierror, socket.error, xmlrpc_client.ProtocolError): self._reload_cache() else: @@ -194,12 +191,20 @@ class InventoryModule(BaseInventoryPlugin, Cacheable): def _get_systems(self): if not self.use_cache or 'systems' not in self._cache.get(self.cache_key, {}): - c = self._get_connection() try: if self.token is not None: - data = c.get_systems(self.token) + data = self.cobbler.get_systems(self.token) else: - data = c.get_systems() + data = self.cobbler.get_systems() + + # If more facts are requested, gather them all from Cobbler + if self.facts_level == "as_rendered": + for i, host in enumerate(data): + self.display.vvvv(f"Gathering all facts for {host['name']}\n") + if self.token is not None: + data[i] = self.cobbler.get_system_as_rendered(host['name'], self.token) + else: + data[i] = self.cobbler.get_system_as_rendered(host['name']) except (socket.gaierror, socket.error, xmlrpc_client.ProtocolError): self._reload_cache() else: @@ -229,6 +234,12 @@ class InventoryModule(BaseInventoryPlugin, Cacheable): # get connection host self.cobbler_url = self.get_option('url') + self.display.vvvv(f'Connecting to {self.cobbler_url}\n') + self.cobbler = xmlrpc_client.Server(self.cobbler_url, allow_none=True) + self.token = None + if self.get_option('user') is not None: + self.token = self.cobbler.login(text_type(self.get_option('user')), text_type(self.get_option('password'))) + self.cache_key = self.get_cache_key(path) self.use_cache = cache and self.get_option('cache') @@ -238,6 +249,7 @@ class InventoryModule(BaseInventoryPlugin, Cacheable): self.include_profiles = self.get_option('include_profiles') self.group_by = self.get_option('group_by') self.inventory_hostname = self.get_option('inventory_hostname') + self.facts_level = self.get_option('facts_level') for profile in self._get_profiles(): if profile['parent']: @@ -319,7 +331,7 @@ class InventoryModule(BaseInventoryPlugin, Cacheable): # Add host to groups specified by group_by fields for group_by in self.group_by: - if host[group_by] == '<>': + if host[group_by] == '<>' or host[group_by] == '': groups = [] else: groups = [host[group_by]] if isinstance(host[group_by], str) else host[group_by]