diff --git a/plugins/inventory/netbird.py b/plugins/inventory/netbird.py index df0ed05..b844dd2 100644 --- a/plugins/inventory/netbird.py +++ b/plugins/inventory/netbird.py @@ -60,7 +60,7 @@ from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cachea from ansible.utils.display import Display # Specific for the NetbirdAPI Class -import json +import json, jsonpickle try: import requests @@ -74,9 +74,6 @@ display = Display() class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): NAME = "dominion_solutions.netbird" - - _redirected_names = ["netbird", "dominion_solutions.netbird"] - _load_name = NAME def _build_client(self, loader): @@ -100,11 +97,8 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): def _add_groups(self): """ Add peer groups to the inventory. """ self.netbird_groups = set( - filter(None, [ - peer.data.get("groups") - for peer - in self.peers - ]) + filter(None, + [group[0].get('name') for group in [item.data.get('groups') for l in self.peers for item in self.peers]]) ) for group in self.netbird_groups: self.inventory.add_group(group) @@ -113,7 +107,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): """ Add peers to the groups in the inventory. """ for peer in self.peers: for group in peer.data.get("groups"): - self.inventory.add_host(peer.name, group=group) + self.inventory.add_host(peer.label, group=group.get('name')) def _get_peer_inventory(self): """Get the inventory from the Netbird API""" @@ -128,8 +122,15 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): groups = self.get_option('groups') if groups: self.peers = [ + # 202410221-MJH: This list comprehension that filters the peers is a little hard to read. I'm sorry. peer for peer in self.peers - if any(group in peer.data['groups'] for group in groups) + if any( + group + in [ + # Emulate a pluck here to grab the group names from the peer data. + g.get('name') for g in peer.data.get('groups') + ] + for group in groups) ] def _add_hostvars_for_peers(self): @@ -141,7 +142,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): if ip_style == 'api' and hostvar_key in ['ip', 'ipv6']: continue self.inventory.set_variable( - peer.name, + peer.label, hostvar_key, hostvars[hostvar_key] ) @@ -191,7 +192,6 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): self.include_disconnected = self.get_option('include_disconnected') self._build_client(loader) self._get_peer_inventory() - self._add_hostvars_for_peers() if update_cache: self._cache[cache_key] = self._cacheable_inventory() @@ -199,9 +199,32 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): self.populate() def populate(self): + """ Populate the inventory with the peers from the Netbird API. """ + strict = self.get_option('strict') + self._filter_by_config() + self._add_groups() self._add_peers_to_group() + self._add_hostvars_for_peers() + for peer in self.peers: + variables = self.inventory.get_host(peer.label).get_vars() + self._add_host_to_composed_groups( + self.get_option('groups'), + variables, + peer.label, + strict=strict) + self._add_host_to_keyed_groups( + self.get_option('keyed_groups'), + variables, + peer.label, + strict=strict) + self._set_composite_vars( + self.get_option('compose'), + variables, + peer.label, + strict=strict) + raise AnsibleError(f"self.inventory: {jsonpickle.encode(self.inventory)}") # This is a very limited wrapper for the netbird API. @@ -225,7 +248,7 @@ class NetbirdApi: response = requests.request("GET", url, headers=headers) peer_json = json.loads(response.text) for current_peer_map in peer_json: - current_peer = Peer(current_peer_map["hostname"], current_peer_map["id"], current_peer_map) + current_peer = Peer(current_peer_map["hostname"], current_peer_map['dns_label'], current_peer_map["id"], current_peer_map) peers.append(current_peer) return peers @@ -290,7 +313,8 @@ class Peer: def _raw_json(self): return self.data - def __init__(self, name, id, data): + def __init__(self, name, label, id, data): self.name = name + self.label = label self.id = id self.data = data diff --git a/requirements.txt b/requirements.txt index 7cf94d6..350b92c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ cffi==1.16.0 cryptography==42.0.2 epdb==0.15.1 Jinja2==3.1.3 +jsonpickle==3.0.3 MarkupSafe==2.1.5 packaging==23.2 pycparser==2.21 diff --git a/tests/unit/module_utils/inventories/fixtures/netbird.yml b/tests/unit/module_utils/inventories/fixtures/netbird.yml new file mode 100644 index 0000000..417f743 --- /dev/null +++ b/tests/unit/module_utils/inventories/fixtures/netbird.yml @@ -0,0 +1,8 @@ +--- +plugin: dominion_solutions.netbird +api_key: nbp_1234567890123456789012345678901234567 +api_url: https://netbird.example.com/api/v1 +include_disconnected: Yes +ip_style: plain +groups: +- "All" diff --git a/tests/unit/module_utils/inventories/fixtures/peers.json b/tests/unit/module_utils/inventories/fixtures/peers.json new file mode 100644 index 0000000..65715ae --- /dev/null +++ b/tests/unit/module_utils/inventories/fixtures/peers.json @@ -0,0 +1,54 @@ +[ + { + "accessible_peers_count": 1, + "approval_required": false, + "connected": false, + "dns_label": "apple.netbird.cloud", + "groups": [ + { + "id": "2a3b4c5d6e7f8g9h0i1j", + "name": "All", + "peers_count": 2 + } + ], + "hostname": "apple", + "id": "3a7b2c1d4e5f6g8h9i0j", + "ip": "100.0.0.42", + "last_login": "2024-02-10T22:01:27.744131502Z", + "last_seen": "2024-02-11T03:21:42.202104672Z", + "login_expiration_enabled": true, + "login_expired": false, + "name": "apple", + "os": "Linux Mint 21.3", + "ssh_enabled": false, + "ui_version": "netbird-desktop-ui/0.25.7", + "user_id": "auth0|ABC123xyz4567890", + "version": "0.25.7" + }, + { + "accessible_peers_count": 1, + "approval_required": false, + "connected": true, + "dns_label": "banana.netbird.cloud", + "groups": [ + { + "id": "2a3b4c5d6e7f8g9h0i1j", + "name": "All", + "peers_count": 2 + } + ], + "hostname": "banana", + "id": "3a7b2c1d4e5f6g8h9i0j", + "ip": "100.0.0.61", + "last_login": "2024-02-02T11:20:05.934889112Z", + "last_seen": "2024-02-16T16:14:35.853243309Z", + "login_expiration_enabled": false, + "login_expired": false, + "name": "banana", + "os": "Alpine Linux 3.19.1", + "ssh_enabled": false, + "ui_version": "", + "user_id": "", + "version": "0.25.5" + } +] diff --git a/tests/unit/plugins/inventory/test_netbird.py b/tests/unit/plugins/inventory/test_netbird.py index 81fba9f..c5f97eb 100644 --- a/tests/unit/plugins/inventory/test_netbird.py +++ b/tests/unit/plugins/inventory/test_netbird.py @@ -20,7 +20,7 @@ from ansible.utils.display import Display from ansible_collections.dominion_solutions.netbird.plugins.inventory.netbird import InventoryModule, NetbirdApi, Peer from unittest.mock import MagicMock -import json +import json,jsonpickle display = Display() @@ -41,7 +41,7 @@ def netbird_api(): with open('tests/unit/module_utils/inventories/fixtures/peers.json') as peers_file: peers_map = json.load(peers_file) for data in peers_map: - response_data.append(Peer(data['hostname'], data['id'], data)) + response_data.append(Peer(data['hostname'], data['dns_label'], data['id'], data)) mock_netbird_api.ListPeers = MagicMock(return_value=response_data) @@ -73,4 +73,5 @@ def test_get_peer_data(inventory, netbird_api): inventory.client = netbird_api inventory.parse(InventoryData(), loader, path, False) assert inventory.inventory is not None - raise AnsibleError(inventory.inventory) + assert inventory.inventory.hosts is not None + assert len(inventory.inventory.hosts) == 1 diff --git a/tests/unit/requirements.txt b/tests/unit/requirements.txt index 0eb8cae..acc017e 100644 --- a/tests/unit/requirements.txt +++ b/tests/unit/requirements.txt @@ -1 +1,2 @@ requests>=2.31.0 +jsonpickle==3.0.3