Skip to content

Commit

Permalink
Add ability to rename host inventory variables using patterns (netbox…
Browse files Browse the repository at this point in the history
…-community#1273)

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'
```

(cherry picked from commit a271a54)
  • Loading branch information
href committed Jul 5, 2024
1 parent 38e6fb4 commit bf55747
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:

Check warning on line 1 in changelogs/fragments/1273-ability-to-rename-host-inventory-vars.yml

View workflow job for this annotation

GitHub Actions / linting

1:1 [document-start] missing document start "---"
- Add ability to rename variables set on the host by ``netbox.netbox.nb_inventory`` through configuration.
48 changes: 38 additions & 10 deletions plugins/inventory/nb_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,16 @@
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
elements: dict
default: []
"""

EXAMPLES = """
Expand Down Expand Up @@ -364,6 +374,7 @@
import uuid
import math
import os
import re
import datetime
from copy import deepcopy
from functools import partial
Expand Down Expand Up @@ -1909,31 +1920,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 @@ -1969,9 +1986,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 @@ -2150,4 +2167,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 ()
]
36 changes: 36 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,24 @@ 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 bf55747

Please sign in to comment.