diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 7f59751..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,3 +0,0 @@ -image: python:3.17-alpine3.19 -before_script: - - pip install -r requirements.txt diff --git a/dominion_solutions/netbird/plugins/inventory/netbird.py b/dominion_solutions/netbird/plugins/inventory/netbird.py new file mode 100644 index 0000000..8f87c53 --- /dev/null +++ b/dominion_solutions/netbird/plugins/inventory/netbird.py @@ -0,0 +1,104 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = r""" + name: netbird + author: + - Mark Horninger (@dominion.soltuions@mstdn.business) + version_added: "0.0.2" + requirements: + - requests>=2.31.0 + short_description: + description: + - + extends_documentation_fragment: + - constructed + - inventory_cache + options: + api_key: + description: The API Key for the Netbird API. + required: true + type: string + env: + - name: NETBIRD_API_KEY + notes: + - if read in variable context, the file can be interpreted as YAML if the content is valid to the parser. + - this lookup does not understand globbing --- use the fileglob lookup instead. +""" + +EXAMPLES = r""" +""" +from ansible.errors import AnsibleError, AnsibleParserError +from ansible.utils.display import Display +from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable + +# Specific for the NetbirdAPI Class +import requests +import json + +display = Display() + +class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): + NAME="dominion_solutions.netbird" + + def _build_client(self): + """Build the Netbird API Client""" + access_token = self.get_option('api_key') + api_url = self.get_option('api_url') + if access_token is None: + raise AnsibleError("Could not retrieve the Netbird API Key from the configuration sources.") + self.client = NetbirdApi(self.get_option('api_key'), self.get_option('api_url')) + + def _get_peer_inventory(self): + """Get the inventory from the Netbird API""" + return json.loads(self.client.ListPeers()) + + def parse(self, inventory, loader, path, cache=True): + """Dynamically parse the inventory from the Netbird API""" + super(InventoryModule, self).parse(inventory, loader, path) + self.peers = None + + self._read_config_data(path) + cache_key = self.get_cache_key(path) + + if cache: + cache = self.get_option('cache') + update_cache = False + if cache: + try: + self.peers = [Peer(None, i["id"], i) for i in self._cache[cache_key]] + except KeyError: + update_cache = True + + # Check for None rather than False in order to allow + # for empty sets of cached instances + if self.instances is None: + self._build_client(loader) + self._get_peer_inventory() + + if update_cache: + self._cache[cache_key] = self._cacheable_inventory() + + self.populate() + + +### This is a very limited wrapper for the netbird API. +class NetbirdApi: + def __init__ (self, api_key, api_url): + self.api_key = api_key + self.api_url = api_url + def ListPeers(self): + url = f"{self.api_url}/peers" + + headers = { + 'Accept': 'application/json', + 'Authorization': f'Token {self.api_key}' + } + response = requests.request("GET", url, headers=headers) + return response.text + +class Peer: + def __init__(self, name, id, data): + self.name = name + self.id = id + self.data = data diff --git a/dominion_solutions/netbird/tests/unit/plugins/inventory/test_netbird.py b/dominion_solutions/netbird/tests/unit/plugins/inventory/test_netbird.py new file mode 100644 index 0000000..8db392d --- /dev/null +++ b/dominion_solutions/netbird/tests/unit/plugins/inventory/test_netbird.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 Dominion Solutions LLC (https://dominion.solutions) +# SPDX-License-Identifier: MIT + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest +import sys + +from ansible.errors import AnsibleError +from ansible.parsing.dataloader import DataLoader +from ansible.template import Templar +from ansible_collections.community.general.plugins.inventory.linode import InventoryModule + + +@pytest.fixture(scope="module") +def inventory(): + plugin = InventoryModule() + plugin.templar = Templar(loader=DataLoader()) + return plugin + + +def test_missing_access_token_lookup(inventory): + loader = DataLoader() + inventory._options = {'access_token': None} + with pytest.raises(AnsibleError) as error_message: + inventory._build_client(loader) + assert 'Could not retrieve Linode access token' in error_message + + +def test_verify_file(tmp_path, inventory): + file = tmp_path / "foobar.netbird.yml" + file.touch() + assert inventory.verify_file(str(file)) is True + + +def test_verify_file_bad_config(inventory): + assert inventory.verify_file('foobar.netbird.yml') is False diff --git a/requirements.txt b/requirements.txt index c11843b..3527136 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,6 @@ MarkupSafe==2.1.5 packaging==23.2 pycparser==2.21 PyYAML==6.0.1 +requests>=2.31.0 resolvelib==1.0.1 +pytest==8.0.0