Move NPM configuration and client behind plugin boundary
This commit is contained in:
+13
-33
@@ -47,28 +47,27 @@ class EndpointParsingTests(unittest.TestCase):
|
||||
)
|
||||
|
||||
|
||||
class AppConfigEndpointTests(unittest.TestCase):
|
||||
def test_legacy_proxy_ip_remains_supported(self):
|
||||
class AppConfigPolicyTests(unittest.TestCase):
|
||||
def test_explicit_dns_target_is_used(self):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"PROXY_IP": "192.168.1.101"},
|
||||
{
|
||||
"DNS_TARGET_IP": "192.168.1.101",
|
||||
"NPM_ENDPOINTS": "192.168.1.3,192.168.1.4",
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
config = AppConfig.from_env()
|
||||
|
||||
self.assertEqual(
|
||||
tuple(endpoint.identity for endpoint in config.npm_endpoints),
|
||||
("npm://192.168.1.101:81",),
|
||||
config.target_ip,
|
||||
"192.168.1.101",
|
||||
)
|
||||
self.assertEqual(config.target_ip, "192.168.1.101")
|
||||
|
||||
def test_endpoint_list_controls_api_and_proxy_ip_remains_target(self):
|
||||
def test_legacy_proxy_ip_remains_target_fallback(self):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"NPM_ENDPOINTS": (
|
||||
"192.168.1.3,192.168.1.4:8081,192.168.1.5"
|
||||
),
|
||||
"PROXY_IP": "192.168.1.101",
|
||||
},
|
||||
clear=True,
|
||||
@@ -76,33 +75,15 @@ class AppConfigEndpointTests(unittest.TestCase):
|
||||
config = AppConfig.from_env()
|
||||
|
||||
self.assertEqual(
|
||||
tuple(endpoint.identity for endpoint in config.npm_endpoints),
|
||||
(
|
||||
"npm://192.168.1.3:81",
|
||||
"npm://192.168.1.4:8081",
|
||||
"npm://192.168.1.5:81",
|
||||
),
|
||||
config.target_ip,
|
||||
"192.168.1.101",
|
||||
)
|
||||
self.assertEqual(config.target_ip, "192.168.1.101")
|
||||
|
||||
def test_explicit_dns_target_supports_multiple_endpoints(self):
|
||||
def test_dns_target_is_required_without_legacy_fallback(self):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"NPM_ENDPOINTS": "192.168.1.3,192.168.1.4",
|
||||
"DNS_TARGET_IP": "192.168.1.101",
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
config = AppConfig.from_env()
|
||||
|
||||
self.assertEqual(config.target_ip, "192.168.1.101")
|
||||
|
||||
def test_multiple_endpoints_require_target_without_proxy_ip(self):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"NPM_ENDPOINTS": "192.168.1.3,192.168.1.4",
|
||||
"NPM_ENDPOINTS": "192.168.1.3",
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
@@ -113,6 +94,5 @@ class AppConfigEndpointTests(unittest.TestCase):
|
||||
AppConfig.from_env()
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
+120
-112
@@ -16,73 +16,103 @@ from dns_sync.adapters import (
|
||||
from dns_sync.certificates.material import CertificateMaterial
|
||||
from dns_sync.config import AppConfig
|
||||
from dns_sync.object_normalizer import normalize_discovered_object
|
||||
from dns_sync.registry import load_adapter
|
||||
from dns_sync.registry import (
|
||||
configured_adapter_endpoints,
|
||||
load_adapter,
|
||||
)
|
||||
|
||||
|
||||
class NpmAdapterTests(unittest.TestCase):
|
||||
def config(self):
|
||||
with patch.dict(
|
||||
@staticmethod
|
||||
def npm_env(
|
||||
endpoints="192.168.1.3,192.168.1.4",
|
||||
):
|
||||
return patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"NPM_ENDPOINTS": "192.168.1.3,192.168.1.4",
|
||||
"NPM_ENDPOINTS": endpoints,
|
||||
"DNS_TARGET_IP": "192.168.1.101",
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
)
|
||||
|
||||
def config(self):
|
||||
with self.npm_env():
|
||||
return AppConfig.from_env()
|
||||
|
||||
def adapter(self, client):
|
||||
config = self.config()
|
||||
with patch(
|
||||
"dns_sync.plugins.adapters.npm.NpmClient",
|
||||
return_value=client,
|
||||
):
|
||||
return load_adapter(
|
||||
with self.npm_env():
|
||||
config = AppConfig.from_env()
|
||||
endpoints = configured_adapter_endpoints(
|
||||
"npm",
|
||||
config,
|
||||
config.npm_endpoints[0],
|
||||
)
|
||||
|
||||
with patch(
|
||||
"dns_sync.plugins.adapters.npm.NpmClient",
|
||||
return_value=client,
|
||||
):
|
||||
return load_adapter(
|
||||
"npm",
|
||||
config,
|
||||
endpoints[0],
|
||||
)
|
||||
|
||||
def target_adapter_with_sources(
|
||||
self,
|
||||
target_client,
|
||||
source_clients,
|
||||
):
|
||||
config = self.config()
|
||||
|
||||
with patch(
|
||||
"dns_sync.plugins.adapters.npm.NpmClient",
|
||||
return_value=target_client,
|
||||
):
|
||||
adapter = load_adapter(
|
||||
with self.npm_env():
|
||||
config = AppConfig.from_env()
|
||||
endpoints = configured_adapter_endpoints(
|
||||
"npm",
|
||||
config,
|
||||
config.npm_endpoints[1],
|
||||
)
|
||||
|
||||
with patch(
|
||||
"dns_sync.plugins.adapters.npm.NpmClient",
|
||||
return_value=target_client,
|
||||
):
|
||||
adapter = load_adapter(
|
||||
"npm",
|
||||
config,
|
||||
endpoints[1],
|
||||
)
|
||||
|
||||
client_by_identity = {
|
||||
endpoint_id: client
|
||||
for endpoint_id, client in source_clients.items()
|
||||
}
|
||||
|
||||
def client_factory(config, endpoint):
|
||||
def client_factory(endpoint):
|
||||
return client_by_identity[endpoint.identity]
|
||||
|
||||
return adapter, client_factory
|
||||
|
||||
def test_registry_creates_one_adapter_per_endpoint(self):
|
||||
config = self.config()
|
||||
first_client = Mock()
|
||||
second_client = Mock()
|
||||
|
||||
with patch(
|
||||
"dns_sync.plugins.adapters.npm.NpmClient",
|
||||
side_effect=[first_client, second_client],
|
||||
) as client_class:
|
||||
adapters = [
|
||||
load_adapter("npm", config, endpoint)
|
||||
for endpoint in config.npm_endpoints
|
||||
]
|
||||
with self.npm_env():
|
||||
config = AppConfig.from_env()
|
||||
endpoints = configured_adapter_endpoints(
|
||||
"npm",
|
||||
config,
|
||||
)
|
||||
|
||||
with patch(
|
||||
"dns_sync.plugins.adapters.npm.NpmClient",
|
||||
side_effect=[first_client, second_client],
|
||||
) as client_class:
|
||||
adapters = [
|
||||
load_adapter(
|
||||
"npm",
|
||||
config,
|
||||
endpoint,
|
||||
)
|
||||
for endpoint in endpoints
|
||||
]
|
||||
|
||||
self.assertEqual(
|
||||
tuple(adapter.endpoint.identity for adapter in adapters),
|
||||
@@ -517,25 +547,14 @@ class NpmAdapterTests(unittest.TestCase):
|
||||
source_client.certificate_material.assert_not_called()
|
||||
|
||||
def test_certificate_material_source_rejects_conflicting_material(self):
|
||||
config = self.config()
|
||||
|
||||
# Add a third endpoint for this ambiguity test.
|
||||
endpoint_type = type(config.npm_endpoints[0])
|
||||
third_endpoint = endpoint_type(
|
||||
"npm",
|
||||
"192.168.1.5:81",
|
||||
)
|
||||
|
||||
config = __import__(
|
||||
"dataclasses"
|
||||
).replace(
|
||||
config,
|
||||
npm_endpoints=(
|
||||
config.npm_endpoints[0],
|
||||
config.npm_endpoints[1],
|
||||
third_endpoint,
|
||||
),
|
||||
)
|
||||
with self.npm_env(
|
||||
"192.168.1.3,192.168.1.4,192.168.1.5"
|
||||
):
|
||||
config = AppConfig.from_env()
|
||||
endpoints = configured_adapter_endpoints(
|
||||
"npm",
|
||||
config,
|
||||
)
|
||||
|
||||
target_client = Mock()
|
||||
source_a = Mock()
|
||||
@@ -568,22 +587,25 @@ class NpmAdapterTests(unittest.TestCase):
|
||||
)
|
||||
)
|
||||
|
||||
with patch(
|
||||
"dns_sync.plugins.adapters.npm.NpmClient",
|
||||
return_value=target_client,
|
||||
with self.npm_env(
|
||||
"192.168.1.3,192.168.1.4,192.168.1.5"
|
||||
):
|
||||
adapter = load_adapter(
|
||||
"npm",
|
||||
config,
|
||||
config.npm_endpoints[1],
|
||||
)
|
||||
with patch(
|
||||
"dns_sync.plugins.adapters.npm.NpmClient",
|
||||
return_value=target_client,
|
||||
):
|
||||
adapter = load_adapter(
|
||||
"npm",
|
||||
config,
|
||||
endpoints[1],
|
||||
)
|
||||
|
||||
clients = {
|
||||
"npm://192.168.1.3:81": source_a,
|
||||
"npm://192.168.1.5:81": source_b,
|
||||
}
|
||||
|
||||
def client_factory(config, endpoint):
|
||||
def client_factory(endpoint):
|
||||
return clients[endpoint.identity]
|
||||
|
||||
desired = NormalizedObject(
|
||||
@@ -607,24 +629,14 @@ class NpmAdapterTests(unittest.TestCase):
|
||||
adapter._certificate_material_source(desired)
|
||||
|
||||
def test_certificate_material_source_accepts_identical_replicas(self):
|
||||
config = self.config()
|
||||
|
||||
endpoint_type = type(config.npm_endpoints[0])
|
||||
third_endpoint = endpoint_type(
|
||||
"npm",
|
||||
"192.168.1.5:81",
|
||||
)
|
||||
|
||||
config = __import__(
|
||||
"dataclasses"
|
||||
).replace(
|
||||
config,
|
||||
npm_endpoints=(
|
||||
config.npm_endpoints[0],
|
||||
config.npm_endpoints[1],
|
||||
third_endpoint,
|
||||
),
|
||||
)
|
||||
with self.npm_env(
|
||||
"192.168.1.3,192.168.1.4,192.168.1.5"
|
||||
):
|
||||
config = AppConfig.from_env()
|
||||
endpoints = configured_adapter_endpoints(
|
||||
"npm",
|
||||
config,
|
||||
)
|
||||
|
||||
target_client = Mock()
|
||||
source_a = Mock()
|
||||
@@ -660,22 +672,25 @@ class NpmAdapterTests(unittest.TestCase):
|
||||
source_a.certificate_material.return_value = common_material
|
||||
source_b.certificate_material.return_value = common_material
|
||||
|
||||
with patch(
|
||||
"dns_sync.plugins.adapters.npm.NpmClient",
|
||||
return_value=target_client,
|
||||
with self.npm_env(
|
||||
"192.168.1.3,192.168.1.4,192.168.1.5"
|
||||
):
|
||||
adapter = load_adapter(
|
||||
"npm",
|
||||
config,
|
||||
config.npm_endpoints[1],
|
||||
)
|
||||
with patch(
|
||||
"dns_sync.plugins.adapters.npm.NpmClient",
|
||||
return_value=target_client,
|
||||
):
|
||||
adapter = load_adapter(
|
||||
"npm",
|
||||
config,
|
||||
endpoints[1],
|
||||
)
|
||||
|
||||
clients = {
|
||||
"npm://192.168.1.3:81": source_a,
|
||||
"npm://192.168.1.5:81": source_b,
|
||||
}
|
||||
|
||||
def client_factory(config, endpoint):
|
||||
def client_factory(endpoint):
|
||||
return clients[endpoint.identity]
|
||||
|
||||
desired = NormalizedObject(
|
||||
@@ -699,24 +714,14 @@ class NpmAdapterTests(unittest.TestCase):
|
||||
self.assertEqual(resolved, common_material)
|
||||
|
||||
def test_certificate_material_source_tolerates_unavailable_unrelated_source(self):
|
||||
config = self.config()
|
||||
|
||||
endpoint_type = type(config.npm_endpoints[0])
|
||||
third_endpoint = endpoint_type(
|
||||
"npm",
|
||||
"192.168.1.5:81",
|
||||
)
|
||||
|
||||
config = __import__(
|
||||
"dataclasses"
|
||||
).replace(
|
||||
config,
|
||||
npm_endpoints=(
|
||||
config.npm_endpoints[0],
|
||||
config.npm_endpoints[1],
|
||||
third_endpoint,
|
||||
),
|
||||
)
|
||||
with self.npm_env(
|
||||
"192.168.1.3,192.168.1.4,192.168.1.5"
|
||||
):
|
||||
config = AppConfig.from_env()
|
||||
endpoints = configured_adapter_endpoints(
|
||||
"npm",
|
||||
config,
|
||||
)
|
||||
|
||||
target_client = Mock()
|
||||
unavailable = Mock()
|
||||
@@ -743,22 +748,25 @@ class NpmAdapterTests(unittest.TestCase):
|
||||
)
|
||||
available.certificate_material.return_value = material
|
||||
|
||||
with patch(
|
||||
"dns_sync.plugins.adapters.npm.NpmClient",
|
||||
return_value=target_client,
|
||||
with self.npm_env(
|
||||
"192.168.1.3,192.168.1.4,192.168.1.5"
|
||||
):
|
||||
adapter = load_adapter(
|
||||
"npm",
|
||||
config,
|
||||
config.npm_endpoints[1],
|
||||
)
|
||||
with patch(
|
||||
"dns_sync.plugins.adapters.npm.NpmClient",
|
||||
return_value=target_client,
|
||||
):
|
||||
adapter = load_adapter(
|
||||
"npm",
|
||||
config,
|
||||
endpoints[1],
|
||||
)
|
||||
|
||||
clients = {
|
||||
"npm://192.168.1.3:81": unavailable,
|
||||
"npm://192.168.1.5:81": available,
|
||||
}
|
||||
|
||||
def client_factory(config, endpoint):
|
||||
def client_factory(endpoint):
|
||||
return clients[endpoint.identity]
|
||||
|
||||
desired = NormalizedObject(
|
||||
|
||||
+74
-27
@@ -5,48 +5,49 @@ from zipfile import ZipFile
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from dns_sync.adapters import Endpoint
|
||||
from dns_sync.config import AppConfig
|
||||
from dns_sync.certificates.material import CertificateMaterial
|
||||
from dns_sync.npm_client import NpmClient
|
||||
from dns_sync.plugins.adapters.npm.client import NpmClient
|
||||
|
||||
|
||||
class NpmClientEndpointTests(unittest.TestCase):
|
||||
def config(self):
|
||||
return AppConfig.from_env()
|
||||
|
||||
def test_default_client_uses_first_configured_endpoint(self):
|
||||
def test_client_uses_explicit_endpoint(self):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"NPM_ENDPOINTS": "192.168.1.3,192.168.1.4:8081",
|
||||
"DNS_TARGET_IP": "192.168.1.101",
|
||||
"NPM_IDENTITY": "test@example.com",
|
||||
"NPM_SECRET": "not-a-real-secret",
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
config = self.config()
|
||||
endpoint = Endpoint(
|
||||
"npm",
|
||||
"192.168.1.3:81",
|
||||
)
|
||||
with patch(
|
||||
"dns_sync.npm_client.requests.Session.request"
|
||||
"dns_sync.plugins.adapters.npm.client.requests.Session.request"
|
||||
) as request:
|
||||
client = NpmClient(config)
|
||||
client = NpmClient(endpoint)
|
||||
|
||||
request.assert_not_called()
|
||||
self.assertEqual(client.api_url, "http://192.168.1.3:81/api")
|
||||
self.assertEqual(
|
||||
client.api_url,
|
||||
"http://192.168.1.3:81/api",
|
||||
)
|
||||
|
||||
def test_client_can_select_specific_configured_endpoint(self):
|
||||
def test_client_can_select_specific_endpoint(self):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"NPM_ENDPOINTS": "192.168.1.3,192.168.1.4:8081",
|
||||
"DNS_TARGET_IP": "192.168.1.101",
|
||||
"NPM_IDENTITY": "test@example.com",
|
||||
"NPM_SECRET": "not-a-real-secret",
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
config = self.config()
|
||||
client = NpmClient(config, config.npm_endpoints[1])
|
||||
endpoint = Endpoint(
|
||||
"npm",
|
||||
"192.168.1.4:8081",
|
||||
)
|
||||
client = NpmClient(endpoint)
|
||||
|
||||
self.assertEqual(
|
||||
client.api_url,
|
||||
@@ -61,19 +62,66 @@ class NpmClientEndpointTests(unittest.TestCase):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"PROXY_IP": "192.168.1.101",
|
||||
"NPM_IDENTITY": "test@example.com",
|
||||
"NPM_SECRET": "not-a-real-secret",
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
config = self.config()
|
||||
endpoint = Endpoint("verizon", "192.168.1.1")
|
||||
endpoint = Endpoint(
|
||||
"verizon",
|
||||
"192.168.1.1:443",
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(RuntimeError, "npm endpoint"):
|
||||
NpmClient(config, endpoint)
|
||||
with self.assertRaisesRegex(
|
||||
RuntimeError,
|
||||
"npm endpoint",
|
||||
):
|
||||
NpmClient(endpoint)
|
||||
|
||||
def test_client_rejects_invalid_api_scheme(self):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"NPM_IDENTITY": "test@example.com",
|
||||
"NPM_SECRET": "not-a-real-secret",
|
||||
"PROXY_API_SCHEME": "ftp",
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
with self.assertRaisesRegex(
|
||||
RuntimeError,
|
||||
"PROXY_API_SCHEME",
|
||||
):
|
||||
NpmClient(
|
||||
Endpoint(
|
||||
"npm",
|
||||
"192.168.1.3:81",
|
||||
)
|
||||
)
|
||||
|
||||
def test_client_owns_tls_verification_setting(self):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"NPM_IDENTITY": "test@example.com",
|
||||
"NPM_SECRET": "not-a-real-secret",
|
||||
"PROXY_API_SCHEME": "https",
|
||||
"PROXY_VERIFY_TLS": "false",
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
client = NpmClient(
|
||||
Endpoint(
|
||||
"npm",
|
||||
"192.168.1.3:81",
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
client.api_url,
|
||||
"https://192.168.1.3:81/api",
|
||||
)
|
||||
self.assertFalse(client.verify_tls)
|
||||
|
||||
|
||||
class NpmClientMutationTests(unittest.TestCase):
|
||||
@@ -81,17 +129,16 @@ class NpmClientMutationTests(unittest.TestCase):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"NPM_ENDPOINTS": "192.168.1.3",
|
||||
"DNS_TARGET_IP": "192.168.1.101",
|
||||
"NPM_IDENTITY": "test@example.com",
|
||||
"NPM_SECRET": "not-a-real-secret",
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
config = AppConfig.from_env()
|
||||
return NpmClient(
|
||||
config,
|
||||
config.npm_endpoints[0],
|
||||
Endpoint(
|
||||
"npm",
|
||||
"192.168.1.3:81",
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -228,14 +228,13 @@ class PluginRegistryTests(unittest.TestCase):
|
||||
"npm",
|
||||
"192.168.1.3:81",
|
||||
)
|
||||
config = SimpleNamespace(
|
||||
npm_endpoints=(endpoint,),
|
||||
)
|
||||
config = SimpleNamespace()
|
||||
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"ENABLED_PLUGINS": "npm",
|
||||
"NPM_ENDPOINTS": "192.168.1.3",
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
|
||||
@@ -24,6 +24,7 @@ class VerizonAdapterTests(unittest.TestCase):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"DNS_TARGET_IP": "192.168.1.101",
|
||||
"NPM_ENDPOINTS": "192.168.1.3",
|
||||
"VERIZON_ENDPOINTS": "192.168.1.1",
|
||||
"ENABLED_PLUGINS": "npm,verizon",
|
||||
@@ -141,6 +142,7 @@ class VerizonAdapterTests(unittest.TestCase):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"DNS_TARGET_IP": "192.168.1.101",
|
||||
"NPM_ENDPOINTS": "192.168.1.3",
|
||||
"VERIZON_ENDPOINTS": "192.168.1.1:8443",
|
||||
"ENABLED_PLUGINS": "npm,verizon",
|
||||
|
||||
Reference in New Issue
Block a user