Getting closer to where I want to be

This commit is contained in:
Mark J. Horninger 2024-02-20 13:47:41 -05:00
parent b601dc270b
commit e287ab5526
3 changed files with 71 additions and 25 deletions

View File

@ -3,22 +3,21 @@
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
DOCUMENTATION = r""" DOCUMENTATION = r'''
name: netbird name: netbird
author: Mark J. Horninger (@spam-n-eggs) author: Mark J. Horninger (@spam-n-eggs)
version_added: "0.0.2" version_added: 0.0.2
requirements: requirements:
- requests>=2.31.0 - requests>=2.31.0
short_description: Get inventory from the Netbird API short_description: Get inventory from the Netbird API
description: description:
- Get inventory from the Netbird API. Allows for filtering based on Netbird Tags / Groups. - Get inventory from the Netbird API. Allows for filtering based on Netbird Tags / Groups.
extends_documentation_fragment: extends_documentation_fragment:
- constructed - constructed
- inventory_cache - inventory_cache
options: options:
cache: cache:
description: Cache plugin output to a file description: Cache plugin output to a file
type: boolean type: boolean
@ -28,22 +27,22 @@ DOCUMENTATION = r"""
required: true required: true
choices: ['netbird', 'dominion_solutions.netbird'] choices: ['netbird', 'dominion_solutions.netbird']
api_key: api_key:
description: The API Key for the Netbird API. description: The API Key for the Netbird API.
required: true required: true
type: string type: string
env: env:
- name: NETBIRD_API_KEY - name: NETBIRD_API_KEY
api_url: api_url:
description: The URL for the Netbird API. description: The URL for the Netbird API.
required: true required: true
type: string type: string
env: env:
- name: NETBIRD_API_URL - name: NETBIRD_API_URL
disconnected: include_disconnected:
description: Whether or not to include disconnected peers in the inventory description: Whether or not to include disconnected peers in the inventory
type: boolean type: boolean
default: false default: false
""" '''
EXAMPLES = r""" EXAMPLES = r"""
""" """
@ -51,9 +50,11 @@ EXAMPLES = r"""
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
from ansible.utils.display import Display
# Specific for the NetbirdAPI Class # Specific for the NetbirdAPI Class
import json import json
try: try:
import requests import requests
except ImportError: except ImportError:
@ -61,13 +62,19 @@ except ImportError:
else: else:
HAS_NETBIRD_API_LIBS = True HAS_NETBIRD_API_LIBS = True
display = Display()
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
NAME = "dominion_solutions.netbird" NAME = "dominion_solutions.netbird"
_redirected_names = ["netbird", "dominion_solutions.netbird"]
_load_name = NAME
def _build_client(self, loader): def _build_client(self, loader):
"""Build the Netbird API Client""" """Build the Netbird API Client"""
display.v("Building the Netbird API Client.")
api_key = self.get_option('api_key') api_key = self.get_option('api_key')
api_url = self.get_option('api_url') api_url = self.get_option('api_url')
if self.templar.is_template(api_key): if self.templar.is_template(api_key):
@ -80,13 +87,17 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
if api_url is None: if api_url is None:
raise AnsibleError("Could not retrieve the Netbird API URL from the configuration sources.") raise AnsibleError("Could not retrieve the Netbird API URL from the configuration sources.")
display.v(f"Set up the Netbird API Client with the URL: {api_url}")
self.client = NetbirdApi(api_key, api_url) self.client = NetbirdApi(api_key, api_url)
def _get_peer_inventory(self): def _get_peer_inventory(self):
"""Get the inventory from the Netbird API""" """Get the inventory from the Netbird API"""
if self.disconnected is False: self.display.v("Getting the inventory from the Netbird API.")
if self.include_disconnected is False:
self.display.vv("Filtering out disconnected peers.")
self.peers = [peer for peer in self.client.ListPeers() if peer.data["connected"] is True] self.peers = [peer for peer in self.client.ListPeers() if peer.data["connected"] is True]
else: else:
display.vv("Including disconnected peers.")
self.peers = self.client.ListPeers() self.peers = self.client.ListPeers()
def _filter_by_config(self): def _filter_by_config(self):
@ -98,16 +109,23 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
if any(group in peer.groups for group in groups) if any(group in peer.groups for group in groups)
] ]
def verify_file(self, path):
"""Verify the Linode configuration file."""
if super(InventoryModule, self).verify_file(path):
endings = ('netbird.yaml', 'netbird.yml')
if any((path.endswith(ending) for ending in endings)):
return True
return False
def parse(self, inventory, loader, path, cache=True): def parse(self, inventory, loader, path, cache=True):
"""Dynamically parse the inventory from the Netbird API""" """Dynamically parse the inventory from the Netbird API"""
super(InventoryModule, self).parse(inventory, loader, path) super(InventoryModule, self).parse(inventory, loader, path)
if not HAS_NETBIRD_API_LIBS: if not HAS_NETBIRD_API_LIBS:
raise AnsibleError("the Netbird Dynamic inventory requires Requests.") raise AnsibleError("the Netbird Dynamic inventory requires Requests.")
self._options = self._read_config_data(path)
self.peers = None self.peers = None
self.disconnected = self.get_option('disconnected')
self._read_config_data(path)
cache_key = self.get_cache_key(path) cache_key = self.get_cache_key(path)
if cache: if cache:
@ -122,6 +140,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
# Check for None rather than False in order to allow # Check for None rather than False in order to allow
# for empty sets of cached instances # for empty sets of cached instances
if self.peers is None: if self.peers is None:
self.include_disconnected = self.get_option('include_disconnected')
self._build_client(loader) self._build_client(loader)
self._get_peer_inventory() self._get_peer_inventory()

View File

@ -2,6 +2,7 @@ ansible>=9.2.0
ansible-core>=2.16.3 ansible-core>=2.16.3
cffi==1.16.0 cffi==1.16.0
cryptography==42.0.2 cryptography==42.0.2
epdb==0.15.1
Jinja2==3.1.3 Jinja2==3.1.3
MarkupSafe==2.1.5 MarkupSafe==2.1.5
packaging==23.2 packaging==23.2

View File

@ -7,13 +7,16 @@ __metaclass__ = type
import pytest import pytest
# TODO: Reenable this if needed. # TODO: Reenable this if needed.
# import sys # import sys
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.parsing.dataloader import DataLoader from ansible.parsing.dataloader import DataLoader
from ansible.template import Templar from ansible.template import Templar
from ansible_collections.dominion_solutions.netbird.plugins.inventory.netbird import InventoryModule from ansible_collections.dominion_solutions.netbird.plugins.inventory.netbird import InventoryModule, NetbirdApi, Peer
from unittest.mock import MagicMock
import json
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
@ -23,6 +26,20 @@ def inventory():
return plugin return plugin
@pytest.fixture(scope="module")
def netbird_api():
mock_netbird_api = NetbirdApi(None, None)
response_data = []
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))
mock_netbird_api.ListPeers = MagicMock(return_value=response_data)
return mock_netbird_api
def test_missing_access_token_lookup(inventory): def test_missing_access_token_lookup(inventory):
loader = DataLoader() loader = DataLoader()
inventory._options = {'api_key': None, 'api_url': None} inventory._options = {'api_key': None, 'api_url': None}
@ -39,3 +56,12 @@ def test_verify_file(tmp_path, inventory):
def test_verify_file_bad_config(inventory): def test_verify_file_bad_config(inventory):
assert inventory.verify_file('foobar.netbird.yml') is False assert inventory.verify_file('foobar.netbird.yml') is False
def test_get_peer_data(inventory, netbird_api):
loader = DataLoader()
path = 'tests/unit/module_utils/inventories/fixtures/netbird.yml'
inventory._build_client = MagicMock()
inventory.client = netbird_api
inventory.parse(dict(), loader, path, False)
assert inventory.peers is not None