Skip to content

Commit

Permalink
Add ability to rename host inventory variables using patterns
Browse files Browse the repository at this point in the history
Certain environments may have conflicts with the variables given by
Netbox, and the variables used in the existing Ansible codebase.

With this change, such cases can be worked around by renaming individual
variables, or whole groups of them.

For example, this will rename all `cluster*` variables to have a
`netbox__` prefix instead (e.g., `cluster_group` -> `netbox__cluster_group`):

```yaml
rename_variables:
  - pattern: 'cluster(.*)'
    repl: 'netbox__cluster\1'
```

Uses a list, instead of a dict, to ensure that the order of evaluation
is strictly defined across all Python versions, and to add the ability
to exclude certain variables from being rewritten. For example:

```yaml
rename_variables:
  # Keep cluster_type the same
  - pattern: 'cluster_type'
    repl: 'cluster_type'

  # Rename all other cluster* variables
  - pattern: 'cluster(.*)'
    repl: 'netbox__cluster\1'
```
  • Loading branch information
href committed Jun 24, 2024
1 parent a157002 commit b9e9245
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 10 deletions.
48 changes: 38 additions & 10 deletions plugins/inventory/nb_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from __future__ import absolute_import, division, print_function

import re

__metaclass__ = type

DOCUMENTATION = """
Expand Down Expand Up @@ -249,6 +251,15 @@
description: Use out of band IP as `ansible host`
type: boolean
default: false
rename_variables:
description:
- Rename variables evaluated by nb_inventory, before writing them.
- Each list entry contains a dict with a 'pattern' and a 'repl'.
- Both 'pattern' and 'repl' are regular expressions.
- The first matching expression is used, subsequent matches are ignored.
- Internally `re.sub` is used.
type: list
defaut: []
"""

EXAMPLES = """
Expand Down Expand Up @@ -1905,31 +1916,37 @@ def _setup_nested_groups(self, group, lookup, parent_lookup):

return transformed_group_names

def _set_variable(self, hostname, key, value):
for item in self.rename_variables:
if item["pattern"].match(key):
key = item["pattern"].sub(item["repl"], key)
break

self.inventory.set_variable(hostname, key, value)

def _fill_host_variables(self, host, hostname):
extracted_primary_ip = self.extract_primary_ip(host=host)
if extracted_primary_ip:
self.inventory.set_variable(hostname, "ansible_host", extracted_primary_ip)
self._set_variable(hostname, "ansible_host", extracted_primary_ip)

if self.ansible_host_dns_name:
extracted_dns_name = self.extract_dns_name(host=host)
if extracted_dns_name:
self.inventory.set_variable(
hostname, "ansible_host", extracted_dns_name
)
self._set_variable(hostname, "ansible_host", extracted_dns_name)

extracted_primary_ip4 = self.extract_primary_ip4(host=host)
if extracted_primary_ip4:
self.inventory.set_variable(hostname, "primary_ip4", extracted_primary_ip4)
self._set_variable(hostname, "primary_ip4", extracted_primary_ip4)

extracted_primary_ip6 = self.extract_primary_ip6(host=host)
if extracted_primary_ip6:
self.inventory.set_variable(hostname, "primary_ip6", extracted_primary_ip6)
self._set_variable(hostname, "primary_ip6", extracted_primary_ip6)

extracted_oob_ip = self.extract_oob_ip(host=host)
if extracted_oob_ip:
self.inventory.set_variable(hostname, "oob_ip", extracted_oob_ip)
self._set_variable(hostname, "oob_ip", extracted_oob_ip)
if self.oob_ip_as_primary_ip:
self.inventory.set_variable(hostname, "ansible_host", extracted_oob_ip)
self._set_variable(hostname, "ansible_host", extracted_oob_ip)

for attribute, extractor in self.group_extractors.items():
extracted_value = extractor(host)
Expand Down Expand Up @@ -1965,9 +1982,9 @@ def _fill_host_variables(self, host, hostname):
)
):
for key, value in extracted_value.items():
self.inventory.set_variable(hostname, key, value)
self._set_variable(hostname, key, value)
else:
self.inventory.set_variable(hostname, attribute, extracted_value)
self._set_variable(hostname, attribute, extracted_value)

def _get_host_virtual_chassis_master(self, host):
virtual_chassis = host.get("virtual_chassis", None)
Expand Down Expand Up @@ -2146,4 +2163,15 @@ def parse(self, inventory, loader, path, cache=True):
self.ansible_host_dns_name = self.get_option("ansible_host_dns_name")
self.racks = self.get_option("racks")

# Compile regular expressions, if any
self.rename_variables = self.parse_rename_variables(
self.get_option("rename_variables")
)

self.main()

def parse_rename_variables(self, rename_variables):
return [
{"pattern": re.compile(i["pattern"]), "repl": i["repl"]}
for i in rename_variables or ()
]
34 changes: 34 additions & 0 deletions tests/unit/inventory/test_nb_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@
)


class MockInventory:

def __init__(self):
self.variables = {}

def set_variable(self, hostname, key, value):
if hostname not in self.variables:
self.variables[hostname] = {}

self.variables[hostname][key] = value


@pytest.fixture
def inventory_fixture(
allowed_device_query_parameters_fixture, allowed_vm_query_parameters_fixture
Expand All @@ -46,6 +58,9 @@ def inventory_fixture(
inventory.allowed_device_query_parameters = allowed_device_query_parameters_fixture
inventory.allowed_vm_query_parameters = allowed_vm_query_parameters_fixture

# Inventory mock, to validate what has been set via inventory.inventory.set_variable
inventory.inventory = MockInventory()

return inventory


Expand Down Expand Up @@ -260,3 +275,22 @@ def test_extract_custom_fields(inventory_fixture, custom_fields, expected):
)

assert extracted_custom_fields == expected


def test_rename_variables(inventory_fixture):
inventory_fixture.rename_variables = inventory_fixture.parse_rename_variables((
{"pattern": r"cluster(.*)", "repl": r'netbox_cluster\1'},
{"pattern": r"ansible_host", "repl": r"host"},
))

inventory_fixture._set_variable("host", "ansible_fqdn", "host.example.org")
inventory_fixture._set_variable("host", "ansible_host", "host")
inventory_fixture._set_variable("host", "cluster", "staging")
inventory_fixture._set_variable("host", "cluster_id", "0xdeadbeef")

assert inventory_fixture.inventory.variables["host"] == {
"ansible_fqdn": "host.example.org",
"host": "host",
"netbox_cluster": "staging",
"netbox_cluster_id": "0xdeadbeef"
}

0 comments on commit b9e9245

Please sign in to comment.