mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
834 commits from the Tor onion-service branch against 299 of ours, from merge base7b646bdece: 127 conflicted files, 3506 files changed. The two sides are largely orthogonal. Theirs adds svc-net-tor and a `tor` service across the role tree, plus two sweeping refactors: shipped scripts sorted by language (files/ruby/, files/python/, files/shell/, scripts/lint/*.sh) and the unit suite moved under tests/unit/python/. Ours is requirement 036, the native AI gateway, plus the MCP role work. Most conflicts were both sides appending to the same service map, resolved as unions. Resolutions that were not unions: Credentials moved namespace. Theirs renamed meta/schema.yml to meta/secrets.yml and re-addressed credentials as secrets.credentials.<name>. Git carried our litellm_api_key blocks through the rename, but every consumer we added still read 'credentials.<name>', which no longer resolves. All 45 call sites are migrated. plugins/filter/container_volumes.py is deleted by theirs and relocated to plugins/lookup/container_volumes.py, where it carries the same tmpfs defect ourf85996b43had just fixed: a hard-coded {"size": ...} that silently drops a per mount `mode`. The fix is re-applied in the relocated file, as that commit's merge note anticipated. compose.yml keeps only INFINITO_PLAYWRIGHT_REPORTS_BASE_DIR. Theirs also forwards INFINITO_PLAYWRIGHT_STAGE_BASE_DIR; that line is deliberately dropped, per the analysis recorded in45804e9ad-- STAGE has a drift-proof role-vars fallback, while forwarding it promotes a setdefault-pinnable .env value above that SPOT. tasks/stages/01_constructor.yml takes their include, ./tasks/utils/setup/group.yml, because their refactor removed tasks/groups/*-roles.yml, leaving our include target nonexistent. Our task name is kept, since svc-virt is in the loop. Makefile swarm-zombie is a union: their validate plus teardown, then our lab_subnet cleanup, which their side dropped while leaving lab_subnet.sh as the only orphaned reference in the file. 01_bootstrap.sh keeps our deterministic --ip pinning, which distinguishes an already-attached node from a real failure, over their `|| true`. Their language-sorted layout orphaned references our side still held: zammad's two scripts/apply/*.rb, jenkins' java-with-ca-trust.sh, and the notify topics their handlers renamed to hyphenated form. All corrected. roles/sys-svc-compose/files/shell/swarm/stack_ready.sh gains our latched-update block: paused, rollback_paused, rollback_started and rollback_completed exit 2 rather than polling a state that cannot resolve without another deploy. config_from_run is unioned to (title, logged, jobs); the production caller in trigger/__main__.py now passes jobs, without which our suite suppression would have been inert. READMEs are regenerated rather than hand-merged: 15 LiteLLM rows and 91 Tor rows now derive from the merged metadata, which is itself evidence the meta merge is coherent. Verified: 5161 unit tests pass. The single failure, test_query.py TestSortSpec, also fails on fork/feature/svc-net-tor atb51d2f9919, measured in a baseline worktree -- so this merge introduces no new unit-test failure. Two modules are excluded from that run because `baudolo`, a dependency their branch introduces, is not installed on this host. Not verified: no deploy has run against the merged tree, and make quality has not been run end to end here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1120 lines
39 KiB
Python
1120 lines
39 KiB
Python
"""Unit tests for the pure-Python networks renderer that backs the
|
|
``compose_networks`` and ``container_networks`` lookup plugins.
|
|
|
|
The integration test in ``tests/integration/infrastructure/compose/
|
|
test_networks_render.py`` pins the full rendered YAML for representative
|
|
scenarios; these unit tests cover the helper predicates and edge cases
|
|
(empty registries, missing modes, alias defaults, canonical aliases) so
|
|
regressions surface with a precise failure rather than a snapshot diff.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import unittest
|
|
from typing import ClassVar
|
|
|
|
from utils.networks.render import (
|
|
_coerce_bool,
|
|
_compute_attachments,
|
|
_is_consumer,
|
|
_own_shared_net_provider,
|
|
_suppress_default,
|
|
compute_external_network_roles,
|
|
render_compose_networks,
|
|
render_container_networks,
|
|
shared_network_compose_key,
|
|
)
|
|
|
|
|
|
def _entity_name(role: str) -> str:
|
|
return role.rsplit("-", 1)[-1]
|
|
|
|
|
|
def _const_lookup_config(**values):
|
|
def _lookup(_app, path, default):
|
|
return values.get(path, default)
|
|
|
|
return _lookup
|
|
|
|
|
|
def _const_lookup_database(**values):
|
|
def _lookup(_app, key):
|
|
return values.get(key, "")
|
|
|
|
return _lookup
|
|
|
|
|
|
class TestCoerceBool(unittest.TestCase):
|
|
def test_bool_passthrough(self):
|
|
self.assertTrue(_coerce_bool(True))
|
|
self.assertFalse(_coerce_bool(False))
|
|
|
|
def test_string_truthy_set(self):
|
|
for v in ("true", "True", "TRUE", "1", "yes", "Yes"):
|
|
self.assertTrue(_coerce_bool(v), msg=v)
|
|
|
|
def test_string_falsy_set(self):
|
|
for v in ("false", "no", "0", "", "anything-else"):
|
|
self.assertFalse(_coerce_bool(v), msg=v)
|
|
|
|
def test_none_is_false(self):
|
|
self.assertFalse(_coerce_bool(None))
|
|
|
|
def test_other_truthy(self):
|
|
self.assertTrue(_coerce_bool(1))
|
|
self.assertTrue(_coerce_bool([0]))
|
|
self.assertFalse(_coerce_bool(0))
|
|
self.assertFalse(_coerce_bool([]))
|
|
|
|
|
|
class TestMcpClientConsumer(unittest.TestCase):
|
|
"""Only a client the provider admitted joins its network."""
|
|
|
|
ADMITTED: ClassVar[dict] = {"mcp.allowed_consumers": ["svc-db-qdrant"]}
|
|
|
|
def _consumer(self, **flags):
|
|
entry = {
|
|
"role": "web-app-homeassistant",
|
|
"overlay": {"consumer": {"kind": "mcp_client"}},
|
|
}
|
|
return _is_consumer(
|
|
entry,
|
|
"svc-db-qdrant",
|
|
_const_lookup_config(**flags),
|
|
_const_lookup_database(),
|
|
)
|
|
|
|
def test_client_joins(self):
|
|
self.assertTrue(
|
|
self._consumer(
|
|
**{
|
|
"mcp.enabled": True,
|
|
"mcp.shared": True,
|
|
"mcp.direction": "client",
|
|
**self.ADMITTED,
|
|
}
|
|
)
|
|
)
|
|
|
|
def test_both_joins(self):
|
|
self.assertTrue(
|
|
self._consumer(
|
|
**{
|
|
"mcp.enabled": True,
|
|
"mcp.shared": True,
|
|
"mcp.direction": "both",
|
|
**self.ADMITTED,
|
|
}
|
|
)
|
|
)
|
|
|
|
def test_server_stays_out(self):
|
|
self.assertFalse(
|
|
self._consumer(
|
|
**{
|
|
"mcp.enabled": True,
|
|
"mcp.shared": True,
|
|
"mcp.direction": "server",
|
|
}
|
|
),
|
|
"a provider on another provider's network is the mesh this kind removes",
|
|
)
|
|
|
|
def test_disabled_stays_out(self):
|
|
self.assertFalse(self._consumer(**{"mcp.direction": "client"}))
|
|
|
|
def test_an_unadmitted_client_stays_out(self):
|
|
self.assertFalse(
|
|
self._consumer(
|
|
**{
|
|
"mcp.enabled": True,
|
|
"mcp.shared": True,
|
|
"mcp.direction": "client",
|
|
"mcp.allowed_consumers": ["web-app-openwebui"],
|
|
}
|
|
),
|
|
"being a client is not an admission; allowed_consumers decides",
|
|
)
|
|
|
|
def test_a_provider_naming_nobody_admits_nobody(self):
|
|
self.assertFalse(
|
|
self._consumer(
|
|
**{
|
|
"mcp.enabled": True,
|
|
"mcp.shared": True,
|
|
"mcp.direction": "client",
|
|
"mcp.allowed_consumers": [],
|
|
}
|
|
)
|
|
)
|
|
|
|
|
|
class TestConsumerKindList(unittest.TestCase):
|
|
"""A provider serving two kinds of consumer declares both on one overlay.
|
|
|
|
Qdrant is the case: applications consume it as a vector database through the
|
|
services flags, and MCP clients must reach its adapter. One overlay per role
|
|
means a single `kind` cannot express both, and the second consumer silently
|
|
has no route.
|
|
"""
|
|
|
|
ENTRY: ClassVar[dict] = {
|
|
"role": "svc-db-qdrant",
|
|
"entity_name": "qdrant",
|
|
"overlay": {
|
|
"consumer": {"kind": ["services_flags", "mcp_client"], "key": "qdrant"}
|
|
},
|
|
}
|
|
|
|
def _consumer(self, application_id, **flags):
|
|
return _is_consumer(
|
|
self.ENTRY,
|
|
application_id,
|
|
_const_lookup_config(**flags),
|
|
_const_lookup_database(),
|
|
)
|
|
|
|
def test_a_service_consumer_still_joins(self):
|
|
self.assertTrue(
|
|
self._consumer(
|
|
"web-app-openwebui",
|
|
**{"services.qdrant.enabled": True, "services.qdrant.shared": True},
|
|
)
|
|
)
|
|
|
|
def test_an_admitted_mcp_client_joins(self):
|
|
self.assertTrue(
|
|
self._consumer(
|
|
"web-app-hermes",
|
|
**{
|
|
"mcp.enabled": True,
|
|
"mcp.shared": True,
|
|
"mcp.direction": "client",
|
|
"mcp.allowed_consumers": ["web-app-hermes"],
|
|
},
|
|
)
|
|
)
|
|
|
|
def test_a_role_matching_neither_kind_stays_out(self):
|
|
self.assertFalse(self._consumer("web-app-unrelated"))
|
|
|
|
|
|
class TestSuppressDefault(unittest.TestCase):
|
|
def test_svc_db_prefix_suppresses(self):
|
|
self.assertTrue(_suppress_default("svc-db-mariadb"))
|
|
self.assertTrue(_suppress_default("svc-db-postgres"))
|
|
|
|
def test_svc_ai_prefix_suppresses(self):
|
|
self.assertTrue(_suppress_default("svc-ai-ollama"))
|
|
|
|
def test_other_prefixes_do_not_suppress(self):
|
|
self.assertFalse(_suppress_default("svc-prx-openresty"))
|
|
self.assertFalse(_suppress_default("web-app-baserow"))
|
|
self.assertFalse(_suppress_default("web-svc-collabora"))
|
|
|
|
|
|
class TestIsConsumer(unittest.TestCase):
|
|
def test_database_kind_all_conditions(self):
|
|
entry = {
|
|
"role": "svc-db-postgres",
|
|
"overlay": {"consumer": {"kind": "database"}},
|
|
}
|
|
lookup_db = _const_lookup_database(
|
|
enabled=True, shared=True, id="svc-db-postgres"
|
|
)
|
|
self.assertTrue(
|
|
_is_consumer(entry, "web-app-baserow", _const_lookup_config(), lookup_db)
|
|
)
|
|
|
|
def test_database_kind_wrong_provider(self):
|
|
entry = {
|
|
"role": "svc-db-mariadb",
|
|
"overlay": {"consumer": {"kind": "database"}},
|
|
}
|
|
lookup_db = _const_lookup_database(
|
|
enabled=True, shared=True, id="svc-db-postgres"
|
|
)
|
|
self.assertFalse(
|
|
_is_consumer(entry, "web-app-baserow", _const_lookup_config(), lookup_db)
|
|
)
|
|
|
|
def test_database_kind_not_shared(self):
|
|
entry = {
|
|
"role": "svc-db-mariadb",
|
|
"overlay": {"consumer": {"kind": "database"}},
|
|
}
|
|
lookup_db = _const_lookup_database(
|
|
enabled=True, shared=False, id="svc-db-mariadb"
|
|
)
|
|
self.assertFalse(
|
|
_is_consumer(entry, "web-app-baserow", _const_lookup_config(), lookup_db)
|
|
)
|
|
|
|
def test_services_flags_explicit_key_and_flags(self):
|
|
entry = {
|
|
"role": "svc-prx-openresty",
|
|
"entity_name": "openresty",
|
|
"overlay": {
|
|
"consumer": {
|
|
"kind": "services_flags",
|
|
"key": "sso",
|
|
"flags": ["enabled"],
|
|
}
|
|
},
|
|
}
|
|
cfg = _const_lookup_config(**{"services.sso.enabled": True})
|
|
self.assertTrue(
|
|
_is_consumer(entry, "web-app-baserow", cfg, _const_lookup_database())
|
|
)
|
|
|
|
def test_services_flags_default_key_from_provides(self):
|
|
entry = {
|
|
"role": "svc-db-openldap",
|
|
"entity_name": "openldap",
|
|
"provides": "ldap",
|
|
"overlay": {},
|
|
}
|
|
cfg = _const_lookup_config(
|
|
**{"services.ldap.enabled": True, "services.ldap.shared": True}
|
|
)
|
|
self.assertTrue(
|
|
_is_consumer(entry, "web-app-bookwyrm", cfg, _const_lookup_database())
|
|
)
|
|
|
|
def test_services_flags_default_key_from_entity_name(self):
|
|
entry = {
|
|
"role": "svc-ai-ollama",
|
|
"entity_name": "ollama",
|
|
"overlay": {},
|
|
}
|
|
cfg = _const_lookup_config(
|
|
**{"services.ollama.enabled": True, "services.ollama.shared": True}
|
|
)
|
|
self.assertTrue(
|
|
_is_consumer(entry, "web-app-openwebui", cfg, _const_lookup_database())
|
|
)
|
|
|
|
def test_services_flags_default_flags_require_enabled_and_shared(self):
|
|
entry = {
|
|
"role": "svc-db-openldap",
|
|
"entity_name": "openldap",
|
|
"provides": "ldap",
|
|
"overlay": {},
|
|
}
|
|
cfg = _const_lookup_config(**{"services.ldap.enabled": True})
|
|
self.assertFalse(
|
|
_is_consumer(entry, "web-app-x", cfg, _const_lookup_database())
|
|
)
|
|
|
|
def test_unknown_kind_returns_false(self):
|
|
entry = {"role": "x", "overlay": {"consumer": {"kind": "future_thing"}}}
|
|
self.assertFalse(
|
|
_is_consumer(
|
|
entry, "web-app-x", _const_lookup_config(), _const_lookup_database()
|
|
)
|
|
)
|
|
|
|
def test_web_facing_matches_web_app_and_web_svc(self):
|
|
entry = {
|
|
"role": "svc-prx-openresty",
|
|
"overlay": {"consumer": {"kind": "web_facing"}},
|
|
}
|
|
for app in ("web-app-baserow", "web-svc-logout"):
|
|
self.assertTrue(
|
|
_is_consumer(
|
|
entry, app, _const_lookup_config(), _const_lookup_database()
|
|
),
|
|
msg=app,
|
|
)
|
|
|
|
def test_web_facing_rejects_non_web_roles(self):
|
|
entry = {
|
|
"role": "svc-prx-openresty",
|
|
"overlay": {"consumer": {"kind": "web_facing"}},
|
|
}
|
|
for app in ("svc-db-mariadb", "svc-ai-ollama", "sys-svc-x"):
|
|
self.assertFalse(
|
|
_is_consumer(
|
|
entry, app, _const_lookup_config(), _const_lookup_database()
|
|
),
|
|
msg=app,
|
|
)
|
|
|
|
|
|
class TestIsConsumerOnionSso(unittest.TestCase):
|
|
ENTRY: ClassVar[dict] = {
|
|
"role": "svc-net-tor",
|
|
"overlay": {
|
|
"consumer": {
|
|
"kind": "onion_sso",
|
|
"key": "sso",
|
|
"provider": "web-app-keycloak",
|
|
"provider_key": "tor",
|
|
}
|
|
},
|
|
}
|
|
|
|
@staticmethod
|
|
def _lookup(*, sso, provider_tor):
|
|
def _inner(app, path, default):
|
|
if app == "web-app-keycloak" and path == "services.tor.enabled":
|
|
return provider_tor
|
|
if path == "services.sso.enabled":
|
|
return sso
|
|
return default
|
|
|
|
return _inner
|
|
|
|
def _consumer(self, *, sso, provider_tor, entry=None):
|
|
return _is_consumer(
|
|
entry or self.ENTRY,
|
|
"web-app-nextcloud",
|
|
self._lookup(sso=sso, provider_tor=provider_tor),
|
|
_const_lookup_database(),
|
|
)
|
|
|
|
def test_attaches_only_when_sso_consumer_meets_onion_provider(self):
|
|
cases = [
|
|
(True, True, True),
|
|
(True, False, False),
|
|
(False, True, False),
|
|
(False, False, False),
|
|
]
|
|
for sso, provider_tor, expected in cases:
|
|
with self.subTest(sso=sso, provider_tor=provider_tor):
|
|
self.assertIs(
|
|
self._consumer(sso=sso, provider_tor=provider_tor), expected
|
|
)
|
|
|
|
def test_provider_is_mandatory(self):
|
|
entry = {"role": "svc-net-tor", "overlay": {"consumer": {"kind": "onion_sso"}}}
|
|
self.assertFalse(self._consumer(sso=True, provider_tor=True, entry=entry))
|
|
|
|
def test_provider_key_defaults_to_tor(self):
|
|
entry = {
|
|
"role": "svc-net-tor",
|
|
"overlay": {
|
|
"consumer": {"kind": "onion_sso", "provider": "web-app-keycloak"}
|
|
},
|
|
}
|
|
self.assertTrue(self._consumer(sso=True, provider_tor=True, entry=entry))
|
|
self.assertFalse(self._consumer(sso=True, provider_tor=False, entry=entry))
|
|
|
|
|
|
class TestComputeAttachments(unittest.TestCase):
|
|
def test_skips_canonical_aliases(self):
|
|
registry = {
|
|
"css": {
|
|
"role": "web-svc-cdn",
|
|
"entity_name": "cdn",
|
|
"canonical": "cdn",
|
|
"overlay": {
|
|
"modes": ["swarm"],
|
|
"topology": "shared_net",
|
|
},
|
|
},
|
|
}
|
|
att, default_aliases = _compute_attachments(
|
|
registry,
|
|
"web-app-x",
|
|
"swarm",
|
|
_const_lookup_config(),
|
|
_const_lookup_database(),
|
|
)
|
|
self.assertEqual(att, [])
|
|
self.assertEqual(default_aliases, [])
|
|
|
|
def test_skips_mode_mismatch(self):
|
|
registry = {
|
|
"ldap": {
|
|
"role": "svc-db-openldap",
|
|
"entity_name": "openldap",
|
|
"provides": "ldap",
|
|
"overlay": {
|
|
"modes": ["compose"],
|
|
"topology": "shared_net",
|
|
},
|
|
},
|
|
}
|
|
att, _ = _compute_attachments(
|
|
registry,
|
|
"svc-db-openldap",
|
|
"swarm",
|
|
_const_lookup_config(),
|
|
_const_lookup_database(),
|
|
)
|
|
self.assertEqual(att, [])
|
|
|
|
def test_skips_beacon_entries_for_non_provider(self):
|
|
registry = {
|
|
"sso": {
|
|
"role": "web-app-keycloak",
|
|
"entity_name": "keycloak",
|
|
"provides": "sso",
|
|
"overlay": {
|
|
"modes": ["swarm"],
|
|
"proxy_resolvable": True,
|
|
"aliases": ["auth.example.com"],
|
|
},
|
|
},
|
|
}
|
|
att, default_aliases = _compute_attachments(
|
|
registry,
|
|
"web-app-baserow",
|
|
"swarm",
|
|
_const_lookup_config(**{"services.sso.enabled": True}),
|
|
_const_lookup_database(),
|
|
)
|
|
self.assertEqual(att, [])
|
|
self.assertEqual(default_aliases, [])
|
|
|
|
def test_provider_self_render_uses_default_alias_from_entity_name(self):
|
|
registry = {
|
|
"ldap": {
|
|
"role": "svc-db-openldap",
|
|
"entity_name": "openldap",
|
|
"provides": "ldap",
|
|
"overlay": {
|
|
"modes": ["swarm"],
|
|
"topology": "shared_net",
|
|
},
|
|
},
|
|
}
|
|
att, _ = _compute_attachments(
|
|
registry,
|
|
"svc-db-openldap",
|
|
"swarm",
|
|
_const_lookup_config(),
|
|
_const_lookup_database(),
|
|
)
|
|
self.assertEqual(len(att), 1)
|
|
self.assertEqual(att[0]["aliases"], ["openldap"])
|
|
|
|
def test_proxy_aliases_override_topology_aliases_in_harvest(self):
|
|
"""A shared_net beacon exposes only proxy_aliases to the harvest; its
|
|
own topology aliases (the entity name) must stay off the proxy."""
|
|
registry = {
|
|
"openresty": {
|
|
"role": "svc-prx-openresty",
|
|
"entity_name": "openresty",
|
|
"overlay": {
|
|
"modes": ["swarm"],
|
|
"topology": "shared_net",
|
|
"collect_proxy_resolvable": True,
|
|
},
|
|
},
|
|
"seaweedfs": {
|
|
"role": "web-app-seaweedfs",
|
|
"entity_name": "seaweedfs",
|
|
"overlay": {
|
|
"modes": ["swarm"],
|
|
"topology": "shared_net",
|
|
"proxy_resolvable": True,
|
|
"proxy_aliases": ["api.s3.example.com"],
|
|
"aliases": ["seaweedfs"],
|
|
},
|
|
},
|
|
}
|
|
att, _ = _compute_attachments(
|
|
registry,
|
|
"svc-prx-openresty",
|
|
"swarm",
|
|
_const_lookup_config(),
|
|
_const_lookup_database(),
|
|
)
|
|
provider = next(a for a in att if a["is_provider"])
|
|
self.assertIn("api.s3.example.com", provider["aliases"])
|
|
self.assertNotIn("seaweedfs", provider["aliases"])
|
|
|
|
def test_proxy_resolvable_sweep_skips_canonical_clones(self):
|
|
registry = {
|
|
"openresty": {
|
|
"role": "svc-prx-openresty",
|
|
"entity_name": "openresty",
|
|
"overlay": {"modes": ["swarm"], "topology": "default_net"},
|
|
},
|
|
"real_beacon": {
|
|
"role": "web-app-keycloak",
|
|
"entity_name": "keycloak",
|
|
"provides": "sso",
|
|
"overlay": {
|
|
"modes": ["swarm"],
|
|
"proxy_resolvable": True,
|
|
"aliases": ["auth.example.com"],
|
|
},
|
|
},
|
|
"clone_alias": {
|
|
"role": "web-app-keycloak",
|
|
"entity_name": "keycloak",
|
|
"canonical": "sso",
|
|
"overlay": {
|
|
"modes": ["swarm"],
|
|
"proxy_resolvable": True,
|
|
"aliases": ["auth.example.com"],
|
|
},
|
|
},
|
|
}
|
|
_, default_aliases = _compute_attachments(
|
|
registry,
|
|
"svc-prx-openresty",
|
|
"swarm",
|
|
_const_lookup_config(),
|
|
_const_lookup_database(),
|
|
)
|
|
self.assertEqual(default_aliases, ["auth.example.com"])
|
|
|
|
def test_proxy_resolvable_sweep_skips_self_role(self):
|
|
registry = {
|
|
"openresty": {
|
|
"role": "svc-prx-openresty",
|
|
"entity_name": "openresty",
|
|
"overlay": {
|
|
"modes": ["swarm"],
|
|
"topology": "default_net",
|
|
"proxy_resolvable": True,
|
|
"aliases": ["self-mistake"],
|
|
},
|
|
},
|
|
"sso": {
|
|
"role": "web-app-keycloak",
|
|
"entity_name": "keycloak",
|
|
"provides": "sso",
|
|
"overlay": {
|
|
"modes": ["swarm"],
|
|
"proxy_resolvable": True,
|
|
"aliases": ["auth.example.com"],
|
|
},
|
|
},
|
|
}
|
|
_, default_aliases = _compute_attachments(
|
|
registry,
|
|
"svc-prx-openresty",
|
|
"swarm",
|
|
_const_lookup_config(),
|
|
_const_lookup_database(),
|
|
)
|
|
self.assertEqual(default_aliases.count("self-mistake"), 1)
|
|
self.assertIn("auth.example.com", default_aliases)
|
|
|
|
|
|
class TestRenderComposeNetworks(unittest.TestCase):
|
|
def test_provider_swarm_emits_only_default(self):
|
|
registry = {
|
|
"openresty": {
|
|
"role": "svc-prx-openresty",
|
|
"entity_name": "openresty",
|
|
"overlay": {"modes": ["swarm"], "topology": "default_net"},
|
|
},
|
|
}
|
|
rendered = render_compose_networks(
|
|
application_id="svc-prx-openresty",
|
|
deployment_mode="swarm",
|
|
registry=registry,
|
|
get_entity_name=_entity_name,
|
|
lookup_config=_const_lookup_config(),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertIn("networks:", rendered)
|
|
self.assertIn("default:", rendered)
|
|
self.assertIn("driver: overlay", rendered)
|
|
self.assertNotIn("openresty:\n external", rendered)
|
|
|
|
def test_provider_compose_falls_back_to_bridge(self):
|
|
registry = {
|
|
"openresty": {
|
|
"role": "svc-prx-openresty",
|
|
"entity_name": "openresty",
|
|
"overlay": {"modes": ["swarm"], "topology": "default_net"},
|
|
},
|
|
}
|
|
rendered = render_compose_networks(
|
|
application_id="svc-prx-openresty",
|
|
deployment_mode="compose",
|
|
registry=registry,
|
|
get_entity_name=_entity_name,
|
|
lookup_config=_const_lookup_config(
|
|
**{"networks.local.subnet": "10.0.0.0/24"}
|
|
),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertIn("driver: bridge", rendered)
|
|
self.assertIn("subnet: 10.0.0.0/24", rendered)
|
|
|
|
def test_svc_db_provider_suppresses_default_block(self):
|
|
registry = {
|
|
"mariadb": {
|
|
"role": "svc-db-mariadb",
|
|
"entity_name": "mariadb",
|
|
"overlay": {"modes": ["swarm"], "topology": "shared_net"},
|
|
},
|
|
}
|
|
rendered = render_compose_networks(
|
|
application_id="svc-db-mariadb",
|
|
deployment_mode="swarm",
|
|
registry=registry,
|
|
get_entity_name=_entity_name,
|
|
lookup_config=_const_lookup_config(),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertIn("mariadb:", rendered)
|
|
self.assertIn("external: true", rendered)
|
|
self.assertNotIn("default:", rendered)
|
|
|
|
def test_empty_own_entity_swarm_omits_name(self):
|
|
rendered = render_compose_networks(
|
|
application_id="svc-runner",
|
|
deployment_mode="swarm",
|
|
registry={},
|
|
get_entity_name=lambda _role: "",
|
|
lookup_config=_const_lookup_config(),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertIn("driver: overlay", rendered)
|
|
self.assertNotIn("name:", rendered)
|
|
|
|
def test_node_local_swarm_container_networks_compose_shape(self):
|
|
rendered = render_container_networks(
|
|
application_id="svc-runner",
|
|
deployment_mode="swarm",
|
|
registry={},
|
|
get_entity_name=lambda _role: "",
|
|
lookup_config=_const_lookup_config(),
|
|
lookup_database=_const_lookup_database(),
|
|
node_local=True,
|
|
)
|
|
expected = render_container_networks(
|
|
application_id="svc-runner",
|
|
deployment_mode="compose",
|
|
registry={},
|
|
get_entity_name=lambda _role: "",
|
|
lookup_config=_const_lookup_config(),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertEqual(rendered, expected)
|
|
|
|
def test_own_network_only_drops_the_consumer_networks(self):
|
|
registry = {
|
|
"qdrant": {
|
|
"role": "svc-db-qdrant",
|
|
"entity_name": "qdrant",
|
|
"overlay": {"modes": ["compose"], "topology": "shared_net"},
|
|
},
|
|
"moodle": {
|
|
"role": "web-app-moodle",
|
|
"entity_name": "moodle",
|
|
"overlay": {"modes": ["compose"], "topology": "shared_net"},
|
|
},
|
|
}
|
|
lookup_config = _const_lookup_config(
|
|
**{"services.moodle.enabled": True, "services.moodle.shared": True}
|
|
)
|
|
full = render_container_networks(
|
|
application_id="svc-db-qdrant",
|
|
deployment_mode="compose",
|
|
registry=registry,
|
|
get_entity_name=_entity_name,
|
|
lookup_config=lookup_config,
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
restricted = render_container_networks(
|
|
application_id="svc-db-qdrant",
|
|
deployment_mode="compose",
|
|
registry=registry,
|
|
get_entity_name=_entity_name,
|
|
lookup_config=lookup_config,
|
|
lookup_database=_const_lookup_database(),
|
|
own_network_only=True,
|
|
provider_self_alias=False,
|
|
)
|
|
self.assertIn("moodle:", full)
|
|
self.assertIn("qdrant:", restricted)
|
|
self.assertNotIn("moodle:", restricted)
|
|
self.assertNotIn("aliases:", restricted)
|
|
|
|
def test_own_network_only_emits_nothing_without_an_attachment(self):
|
|
rendered = render_container_networks(
|
|
application_id="web-app-prometheus",
|
|
deployment_mode="compose",
|
|
registry={},
|
|
get_entity_name=_entity_name,
|
|
lookup_config=_const_lookup_config(),
|
|
lookup_database=_const_lookup_database(),
|
|
own_network_only=True,
|
|
)
|
|
self.assertEqual(
|
|
rendered,
|
|
"",
|
|
"a bare 'networks:' key is not a list and compose refuses the file",
|
|
)
|
|
|
|
def test_node_local_swarm_renders_bridge(self):
|
|
rendered = render_compose_networks(
|
|
application_id="svc-runner",
|
|
deployment_mode="swarm",
|
|
registry={},
|
|
get_entity_name=lambda _role: "",
|
|
lookup_config=_const_lookup_config(
|
|
**{"networks.local.subnet": "10.0.0.0/24"}
|
|
),
|
|
lookup_database=_const_lookup_database(),
|
|
node_local=True,
|
|
)
|
|
self.assertNotIn("overlay", rendered)
|
|
self.assertIn("driver: bridge", rendered)
|
|
|
|
def test_empty_own_entity_compose_omits_name(self):
|
|
rendered = render_compose_networks(
|
|
application_id="svc-runner",
|
|
deployment_mode="compose",
|
|
registry={},
|
|
get_entity_name=lambda _role: "",
|
|
lookup_config=_const_lookup_config(
|
|
**{"networks.local.subnet": "10.0.0.0/24"}
|
|
),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertIn("driver: bridge", rendered)
|
|
self.assertIn("subnet: 10.0.0.0/24", rendered)
|
|
self.assertNotIn("name:", rendered)
|
|
|
|
|
|
class TestComputeExternalNetworkRoles(unittest.TestCase):
|
|
def test_returns_consumer_provider_role(self):
|
|
registry = {
|
|
"redis": {
|
|
"role": "svc-db-redis",
|
|
"entity_name": "redis",
|
|
"overlay": {
|
|
"modes": ["swarm"],
|
|
"topology": "shared_net",
|
|
"consumer": {"kind": "services_flags", "key": "redis"},
|
|
},
|
|
},
|
|
}
|
|
cfg = _const_lookup_config(
|
|
**{"services.redis.enabled": True, "services.redis.shared": True}
|
|
)
|
|
roles = compute_external_network_roles(
|
|
application_id="web-app-gitea",
|
|
deployment_mode="swarm",
|
|
registry=registry,
|
|
lookup_config=cfg,
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertEqual(roles, ["svc-db-redis"])
|
|
|
|
def test_matches_external_true_entries_in_rendered_compose(self):
|
|
registry = {
|
|
"redis": {
|
|
"role": "svc-db-redis",
|
|
"entity_name": "redis",
|
|
"overlay": {
|
|
"modes": ["swarm"],
|
|
"topology": "shared_net",
|
|
"consumer": {"kind": "services_flags", "key": "redis"},
|
|
},
|
|
},
|
|
}
|
|
cfg = _const_lookup_config(
|
|
**{"services.redis.enabled": True, "services.redis.shared": True}
|
|
)
|
|
rendered = render_compose_networks(
|
|
application_id="web-app-gitea",
|
|
deployment_mode="swarm",
|
|
registry=registry,
|
|
get_entity_name=_entity_name,
|
|
lookup_config=cfg,
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
roles = compute_external_network_roles(
|
|
application_id="web-app-gitea",
|
|
deployment_mode="swarm",
|
|
registry=registry,
|
|
lookup_config=cfg,
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertIn("redis:\n external: true", rendered)
|
|
self.assertEqual([_entity_name(r) for r in roles], ["redis"])
|
|
|
|
def test_own_default_net_provider_not_returned(self):
|
|
registry = {
|
|
"openresty": {
|
|
"role": "svc-prx-openresty",
|
|
"entity_name": "openresty",
|
|
"overlay": {"modes": ["swarm"], "topology": "default_net"},
|
|
},
|
|
}
|
|
roles = compute_external_network_roles(
|
|
application_id="svc-prx-openresty",
|
|
deployment_mode="swarm",
|
|
registry=registry,
|
|
lookup_config=_const_lookup_config(),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertEqual(roles, [])
|
|
|
|
|
|
class TestRenderContainerNetworks(unittest.TestCase):
|
|
def test_output_starts_with_leading_newline(self):
|
|
registry = {
|
|
"openresty": {
|
|
"role": "svc-prx-openresty",
|
|
"entity_name": "openresty",
|
|
"overlay": {"modes": ["swarm"], "topology": "default_net"},
|
|
},
|
|
}
|
|
rendered = render_container_networks(
|
|
application_id="svc-prx-openresty",
|
|
deployment_mode="swarm",
|
|
registry=registry,
|
|
get_entity_name=_entity_name,
|
|
lookup_config=_const_lookup_config(),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertTrue(rendered.startswith("\n"))
|
|
self.assertIn("networks:", rendered)
|
|
|
|
def test_consumer_attach_uses_dict_empty(self):
|
|
registry = {
|
|
"ldap": {
|
|
"role": "svc-db-openldap",
|
|
"entity_name": "openldap",
|
|
"provides": "ldap",
|
|
"overlay": {"modes": ["swarm"], "topology": "shared_net"},
|
|
},
|
|
}
|
|
rendered = render_container_networks(
|
|
application_id="web-app-bookwyrm",
|
|
deployment_mode="swarm",
|
|
registry=registry,
|
|
get_entity_name=_entity_name,
|
|
lookup_config=_const_lookup_config(
|
|
**{"services.ldap.enabled": True, "services.ldap.shared": True}
|
|
),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertIn("openldap:\n {}", rendered)
|
|
|
|
def test_provider_default_net_skips_self_attach_emits_alias_block(self):
|
|
registry = {
|
|
"openresty": {
|
|
"role": "svc-prx-openresty",
|
|
"entity_name": "openresty",
|
|
"overlay": {"modes": ["swarm"], "topology": "default_net"},
|
|
},
|
|
"sso": {
|
|
"role": "web-app-keycloak",
|
|
"entity_name": "keycloak",
|
|
"provides": "sso",
|
|
"overlay": {
|
|
"modes": ["swarm"],
|
|
"proxy_resolvable": True,
|
|
"aliases": ["auth.example.com"],
|
|
},
|
|
},
|
|
}
|
|
rendered = render_container_networks(
|
|
application_id="svc-prx-openresty",
|
|
deployment_mode="swarm",
|
|
registry=registry,
|
|
get_entity_name=_entity_name,
|
|
lookup_config=_const_lookup_config(),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertNotIn("openresty:\n", rendered)
|
|
self.assertIn("default:", rendered)
|
|
self.assertIn("- auth.example.com", rendered)
|
|
|
|
def test_empty_registry_emits_only_default(self):
|
|
rendered = render_container_networks(
|
|
application_id="web-app-plain",
|
|
deployment_mode="swarm",
|
|
registry={},
|
|
get_entity_name=_entity_name,
|
|
lookup_config=_const_lookup_config(),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertEqual(rendered, "\nnetworks:\n default:")
|
|
|
|
|
|
class TestRenderEncryption(unittest.TestCase):
|
|
def test_swarm_encrypted_false_emits_lowercase_quoted_literal(self):
|
|
rendered = render_compose_networks(
|
|
application_id="web-app-x",
|
|
deployment_mode="swarm",
|
|
registry={},
|
|
get_entity_name=_entity_name,
|
|
lookup_config=_const_lookup_config(),
|
|
lookup_database=_const_lookup_database(),
|
|
swarm_encrypted=False,
|
|
)
|
|
self.assertIn('encrypted: "false"', rendered)
|
|
self.assertNotIn("encrypted: false\n", rendered)
|
|
self.assertNotIn('encrypted: "False"', rendered)
|
|
|
|
|
|
_PROVIDER_REGISTRY = {
|
|
"seaweedfs": {
|
|
"role": "web-app-seaweedfs",
|
|
"entity_name": "seaweedfs",
|
|
"overlay": {"modes": ["compose", "swarm"], "topology": "shared_net"},
|
|
},
|
|
}
|
|
|
|
|
|
class TestOwnSharedNetProvider(unittest.TestCase):
|
|
"""A multi-service objstore provider (seaweedfs/minio) renders its own
|
|
``<entity>`` net as external + a bare auto-IPAM project default (so the
|
|
role subnet, owned by the external net, is never re-requested), and its
|
|
sidecar services attach without the provider alias. Locks the
|
|
objstore-net-fix contract so a render edit cannot silently regress it."""
|
|
|
|
def test_helper_true_only_for_own_shared_net_provider(self):
|
|
own = [
|
|
{"role": "web-app-seaweedfs", "topology": "shared_net", "is_provider": True}
|
|
]
|
|
consumer = [
|
|
{
|
|
"role": "web-app-seaweedfs",
|
|
"topology": "shared_net",
|
|
"is_provider": False,
|
|
}
|
|
]
|
|
self.assertTrue(_own_shared_net_provider(own, "seaweedfs", _entity_name))
|
|
self.assertFalse(_own_shared_net_provider(consumer, "baserow", _entity_name))
|
|
self.assertFalse(_own_shared_net_provider([], "seaweedfs", _entity_name))
|
|
|
|
def test_compose_default_drops_name_and_subnet(self):
|
|
rendered = render_compose_networks(
|
|
application_id="web-app-seaweedfs",
|
|
deployment_mode="compose",
|
|
registry=_PROVIDER_REGISTRY,
|
|
get_entity_name=_entity_name,
|
|
lookup_config=_const_lookup_config(
|
|
**{"networks.local.subnet": "192.168.206.0/24"}
|
|
),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertIn(" seaweedfs:\n external: true", rendered)
|
|
self.assertIn(" default:\n driver: bridge", rendered)
|
|
self.assertNotIn("192.168.206", rendered)
|
|
self.assertNotIn("name: seaweedfs", rendered)
|
|
|
|
def test_swarm_default_is_nameless_overlay(self):
|
|
rendered = render_compose_networks(
|
|
application_id="web-app-seaweedfs",
|
|
deployment_mode="swarm",
|
|
registry=_PROVIDER_REGISTRY,
|
|
get_entity_name=_entity_name,
|
|
lookup_config=_const_lookup_config(),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertIn(" seaweedfs:\n external: true", rendered)
|
|
self.assertIn(" default:\n driver: overlay", rendered)
|
|
self.assertNotIn("name: seaweedfs", rendered)
|
|
|
|
def test_main_publishes_alias_sidecar_does_not(self):
|
|
kwargs = {
|
|
"application_id": "web-app-seaweedfs",
|
|
"deployment_mode": "compose",
|
|
"registry": _PROVIDER_REGISTRY,
|
|
"get_entity_name": _entity_name,
|
|
"lookup_config": _const_lookup_config(),
|
|
"lookup_database": _const_lookup_database(),
|
|
}
|
|
main = render_container_networks(**kwargs)
|
|
sidecar = render_container_networks(provider_self_alias=False, **kwargs)
|
|
self.assertIn("seaweedfs:\n aliases:\n - seaweedfs", main)
|
|
self.assertIn("seaweedfs:\n {}", sidecar)
|
|
self.assertNotIn("- seaweedfs", sidecar)
|
|
|
|
|
|
class TestSharedNetworkComposeKey(unittest.TestCase):
|
|
"""The key under which the pre-created ``<entity>`` network appears in the
|
|
rendered file. Callers stamp it into ``com.docker.compose.network``; docker
|
|
compose resolves a project's networks against that label, so a key that does
|
|
not match the rendered file makes compose adopt the wrong network and skip
|
|
creating the right one."""
|
|
|
|
def _key(self, application_id, registry, deployment_mode="compose"):
|
|
return shared_network_compose_key(
|
|
application_id=application_id,
|
|
deployment_mode=deployment_mode,
|
|
registry=registry,
|
|
get_entity_name=_entity_name,
|
|
lookup_config=_const_lookup_config(),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
|
|
def test_own_shared_net_provider_keys_on_its_entity(self):
|
|
for mode in ("compose", "swarm"):
|
|
with self.subTest(mode=mode):
|
|
self.assertEqual(
|
|
self._key("web-app-seaweedfs", _PROVIDER_REGISTRY, mode),
|
|
"seaweedfs",
|
|
)
|
|
|
|
def test_plain_app_keys_on_default(self):
|
|
for mode in ("compose", "swarm"):
|
|
with self.subTest(mode=mode):
|
|
self.assertEqual(self._key("web-app-baserow", {}, mode), "default")
|
|
|
|
def test_consumer_of_a_foreign_provider_keys_on_default(self):
|
|
registry = {
|
|
"ldap": {
|
|
"role": "svc-db-openldap",
|
|
"entity_name": "openldap",
|
|
"provides": "ldap",
|
|
"overlay": {"modes": ["compose"], "topology": "shared_net"},
|
|
},
|
|
}
|
|
self.assertEqual(self._key("web-app-baserow", registry), "default")
|
|
|
|
def test_node_local_forces_the_compose_answer(self):
|
|
self.assertEqual(
|
|
shared_network_compose_key(
|
|
application_id="web-app-seaweedfs",
|
|
deployment_mode="swarm",
|
|
registry=_PROVIDER_REGISTRY,
|
|
get_entity_name=_entity_name,
|
|
lookup_config=_const_lookup_config(),
|
|
lookup_database=_const_lookup_database(),
|
|
node_local=True,
|
|
),
|
|
"seaweedfs",
|
|
)
|
|
|
|
def test_key_matches_the_key_the_renderer_emits(self):
|
|
"""The contract that makes this a single source: whatever key the
|
|
function reports must be the key the rendered networks block actually
|
|
uses for the entity network."""
|
|
cases = (
|
|
("web-app-seaweedfs", _PROVIDER_REGISTRY),
|
|
("web-app-baserow", {}),
|
|
)
|
|
for application_id, registry in cases:
|
|
for mode in ("compose", "swarm"):
|
|
with self.subTest(application_id=application_id, mode=mode):
|
|
key = self._key(application_id, registry, mode)
|
|
rendered = render_compose_networks(
|
|
application_id=application_id,
|
|
deployment_mode=mode,
|
|
registry=registry,
|
|
get_entity_name=_entity_name,
|
|
lookup_config=_const_lookup_config(
|
|
**{"networks.local.subnet": "192.168.206.0/24"}
|
|
),
|
|
lookup_database=_const_lookup_database(),
|
|
)
|
|
self.assertIn(f" {key}:", rendered)
|
|
entity = _entity_name(application_id)
|
|
if key == "default":
|
|
self.assertIn(f" name: {entity}", rendered)
|
|
else:
|
|
self.assertEqual(key, entity)
|
|
self.assertNotIn(f" name: {entity}", rendered)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|