-
Notifications
You must be signed in to change notification settings - Fork 81
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
inventory: Implement aggregation of multiple variable values
This commit fixes the aggregation of multiple host variables values. Let's say SNow returns something like this: ``` {'u_name': 'First_app', 'u_parent': 'tomcat_aqwidmvs', 'sys_id': 'a7dc8d0633a8d210392d0e570e5c7bb4', 'u_child': 'i101', 'u_level': '1', 'u_child.name': 'i101', 'u_child.ip_address': '10.1.0.101', 'u_parent.support_group': 'Application Development', 'u_parent.name': 'tomcat_aqwidmvs'} {'u_name': 'Second app', 'u_parent': 'tomcat_pxgnodig', 'sys_id': 'f9fc8d0633a8d210392d0e570e5c7bff', 'u_child': 'i101', 'u_level': '1', 'u_child.name': 'i101', 'u_child.ip_address': '10.1.0.101', 'u_parent.support_group': 'Application Development', 'u_parent.name': 'tomcat_pxgnodig'} ``` For the same host, we have two records with different values: u_parent, u_parent.name, u_parent.support_group, u_parent.name. This change will aggreate these values as follows: ``` all: children: ungrouped: hosts: i101: sys_id: a7dc8d0633a8d210392d0e570e5c7bb4 u_child: - ip_address: 10.1.0.101 name: i101 u_level: '1' u_parent: - name: tomcat_aqwidmvs support_group: Application Development u_parent: tomcat_aqwidmvs - name: tomcat_pxgnodig support_group: Application Development u_parent: tomcat_pxgnodig u_type: active ``` To enable this aggregation, the inventory must have: ``` experimental_aggregation: true ```
- Loading branch information
Showing
2 changed files
with
160 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
# -*- coding: utf-8 -*- | ||
# -*- coding: utf8- -*- | ||
# Copyright: (c) 2021, XLAB Steampunk <[email protected]> | ||
# | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
@@ -120,6 +120,12 @@ | |
type: bool | ||
default: false | ||
version_added: 1.3.0 | ||
experimental_aggregation: | ||
description: | ||
- Enable multiple variable values aggregations. | ||
type: bool | ||
default: false | ||
version_added: 2.7.0 | ||
inventory_hostname_source: | ||
type: str | ||
description: | ||
|
@@ -329,6 +335,7 @@ | |
|
||
|
||
import os | ||
import hashlib | ||
|
||
from ansible.errors import AnsibleParserError | ||
from ansible.inventory.group import to_safe_group_name as orig_safe | ||
|
@@ -352,6 +359,76 @@ | |
from ..module_utils.table import TableClient | ||
|
||
|
||
class Aggregator: | ||
def __init__(self, columns): | ||
self.data = dict() | ||
self.tmp = None | ||
|
||
def add(self, host, key, value): | ||
parent, child = self._split(key) | ||
if not self.tmp: | ||
self.tmp = dict() | ||
|
||
if not child: | ||
self.tmp[parent] = value | ||
else: | ||
tmp_parent_data = self.tmp.get(parent) | ||
if not tmp_parent_data: | ||
tmp_parent_data = dict() | ||
|
||
parent_data = dict() | ||
parent_data[child] = value | ||
if isinstance(tmp_parent_data, str): | ||
parent_data[parent] = tmp_parent_data | ||
else: | ||
for k, v in tmp_parent_data.items(): | ||
parent_data[k] = v | ||
|
||
self.tmp[parent] = parent_data | ||
|
||
def commit(self, host): | ||
if not self.tmp: | ||
return | ||
host_data = self.data.get(host, dict()) | ||
for k, v in self.tmp.items(): | ||
if k in host_data: | ||
vv = host_data.get(k) | ||
if isinstance(vv, list): | ||
if not self._is_exists(vv, v): | ||
vv.append(v) | ||
continue | ||
if isinstance(v, dict): | ||
host_data[k] = [v] | ||
continue | ||
host_data[k] = v | ||
self.tmp = None | ||
self.data[host] = host_data | ||
|
||
def aggregate(self, inventory): | ||
for host, data in self.data.items(): | ||
for k, v in data.items(): | ||
inventory.set_variable(host, k, v) | ||
|
||
def _split(self, column): | ||
if "." not in column: | ||
return column, "" | ||
parts = column.split(".") | ||
return parts[0], parts[1] | ||
|
||
def _is_exists(self, items, item): | ||
hash_v = self._hash_dict(item) | ||
for i in items: | ||
if self._hash_dict(i) == hash_v: | ||
return True | ||
return False | ||
|
||
def _hash_dict(self, d): | ||
h = hashlib.sha256() | ||
d_sorted = str(dict(sorted(d.items()))) | ||
h.update(d_sorted.encode()) | ||
return h.hexdigest() | ||
|
||
|
||
def construct_sysparm_query(query, is_encoded_query): | ||
if is_encoded_query: | ||
return query | ||
|
@@ -445,6 +522,11 @@ def set_hostvars(self, host, record, columns): | |
for k in columns: | ||
self.inventory.set_variable(host, k.replace(".", "_"), record[k]) | ||
|
||
def set_host_vars_aggregated(self, host, record, columns, aggregator): | ||
for k in columns: | ||
aggregator.add(host, k, record[k]) | ||
aggregator.commit(host) | ||
|
||
def fill_constructed( | ||
self, | ||
records, | ||
|
@@ -455,16 +537,26 @@ def fill_constructed( | |
keyed_groups, | ||
strict, | ||
enhanced, | ||
exp_aggregation, | ||
): | ||
if exp_aggregation: | ||
aggregator = Aggregator(columns) | ||
|
||
for record in records: | ||
host = self.add_host(record, name_source) | ||
if host: | ||
self.set_hostvars(host, record, columns) | ||
if exp_aggregation: | ||
self.set_host_vars_aggregated(host, record, columns, aggregator) | ||
else: | ||
self.set_hostvars(host, record, columns) | ||
|
||
self._set_composite_vars(compose, record, host, strict) | ||
self._add_host_to_composed_groups(groups, record, host, strict) | ||
self._add_host_to_keyed_groups(keyed_groups, record, host, strict) | ||
if enhanced: | ||
self.fill_enhanced_auto_groups(record, host) | ||
if exp_aggregation: | ||
aggregator.aggregate(self.inventory) | ||
|
||
def fill_enhanced_auto_groups(self, record, host): | ||
for rel_group in record["relationship_groups"]: | ||
|
@@ -569,6 +661,7 @@ def parse(self, inventory, loader, path, cache=True): | |
raise AnsibleParserError(e) | ||
|
||
enhanced = self.get_option("enhanced") | ||
exp_aggregation = self.get_option("experimental_aggregation") | ||
|
||
sysparm_limit = self.get_option("sysparm_limit") | ||
if sysparm_limit: | ||
|
@@ -636,7 +729,7 @@ def parse(self, inventory, loader, path, cache=True): | |
) | ||
enhance_records_with_rel_groups(records, rel_records) | ||
|
||
self._cache[self.cache_key] = {cache_sub_key: records} | ||
self._cache[self.cache_key] = {cache_sub_key: records} | ||
|
||
self.fill_constructed( | ||
records, | ||
|
@@ -647,4 +740,5 @@ def parse(self, inventory, loader, path, cache=True): | |
self.get_option("keyed_groups"), | ||
self.get_option("strict"), | ||
enhanced, | ||
exp_aggregation, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters