fix: merge egress routes across extends
This commit is contained in:
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .manifest import ManifestBottle, ManifestGitEntry
|
from .manifest import ManifestBottle, ManifestGitEntry
|
||||||
|
from .manifest_egress import ManifestEgressConfig
|
||||||
|
|
||||||
|
|
||||||
def resolve_bottles(raws: dict[str, dict[str, object]]) -> dict[str, ManifestBottle]:
|
def resolve_bottles(raws: dict[str, dict[str, object]]) -> dict[str, ManifestBottle]:
|
||||||
@@ -99,9 +100,16 @@ def _merge_bottles(
|
|||||||
else:
|
else:
|
||||||
merged_git = parent.git
|
merged_git = parent.git
|
||||||
|
|
||||||
# Presence-driven full-replace for the remaining list-valued +
|
# egress.routes: missing means inherit; otherwise parent and child
|
||||||
# scalar fields.
|
# route lists concatenate. Other egress scalar fields remain
|
||||||
merged_egress = child.egress if "egress" in child_raw else parent.egress
|
# presence-driven overlays.
|
||||||
|
merged_egress = (
|
||||||
|
_merge_egress(parent.egress, child.egress, child_raw)
|
||||||
|
if "egress" in child_raw
|
||||||
|
else parent.egress
|
||||||
|
)
|
||||||
|
|
||||||
|
# Presence-driven full-replace for the remaining scalar fields.
|
||||||
merged_agent_provider = (
|
merged_agent_provider = (
|
||||||
child.agent_provider
|
child.agent_provider
|
||||||
if "agent_provider" in child_raw
|
if "agent_provider" in child_raw
|
||||||
@@ -140,3 +148,17 @@ def _merge_git_remotes(
|
|||||||
for entry in child:
|
for entry in child:
|
||||||
by_host[entry.UpstreamHost] = entry
|
by_host[entry.UpstreamHost] = entry
|
||||||
return tuple(by_host.values())
|
return tuple(by_host.values())
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_egress(
|
||||||
|
parent: ManifestEgressConfig,
|
||||||
|
child: ManifestEgressConfig,
|
||||||
|
child_raw: dict[str, object],
|
||||||
|
) -> ManifestEgressConfig:
|
||||||
|
from .manifest_egress import ManifestEgressConfig
|
||||||
|
from .manifest_util import as_json_object
|
||||||
|
|
||||||
|
child_egress_raw = as_json_object(child_raw.get("egress"), "child egress")
|
||||||
|
routes = parent.routes + child.routes
|
||||||
|
log = child.Log if "log" in child_egress_raw else parent.Log
|
||||||
|
return ManifestEgressConfig(routes=routes, Log=log)
|
||||||
|
|||||||
@@ -173,10 +173,10 @@ class TestExtendsGitMerge(unittest.TestCase):
|
|||||||
self.assertEqual("Child", m.bottles["child"].git_user.name)
|
self.assertEqual("Child", m.bottles["child"].git_user.name)
|
||||||
|
|
||||||
|
|
||||||
class TestExtendsListsFullReplace(unittest.TestCase):
|
class TestExtendsEgressMerge(unittest.TestCase):
|
||||||
"""egress: remains full-replace when the child declares it."""
|
"""egress.routes merges; egress.log overlays only when declared."""
|
||||||
|
|
||||||
def test_child_egress_replaces_parent_entirely(self):
|
def test_child_egress_routes_merge_with_parent(self):
|
||||||
m = _build(
|
m = _build(
|
||||||
base={"egress": {"routes": [{"host": "a.example.com"}]}},
|
base={"egress": {"routes": [{"host": "a.example.com"}]}},
|
||||||
child={
|
child={
|
||||||
@@ -185,7 +185,7 @@ class TestExtendsListsFullReplace(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
hosts = [r.Host for r in m.bottles["child"].egress.routes]
|
hosts = [r.Host for r in m.bottles["child"].egress.routes]
|
||||||
self.assertEqual(["b.example.com"], hosts)
|
self.assertEqual(["a.example.com", "b.example.com"], hosts)
|
||||||
|
|
||||||
def test_child_omits_egress_inherits(self):
|
def test_child_omits_egress_inherits(self):
|
||||||
m = _build(
|
m = _build(
|
||||||
@@ -195,6 +195,52 @@ class TestExtendsListsFullReplace(unittest.TestCase):
|
|||||||
hosts = [r.Host for r in m.bottles["child"].egress.routes]
|
hosts = [r.Host for r in m.bottles["child"].egress.routes]
|
||||||
self.assertEqual(["a.example.com"], hosts)
|
self.assertEqual(["a.example.com"], hosts)
|
||||||
|
|
||||||
|
def test_child_egress_log_inherits_parent_routes(self):
|
||||||
|
m = _build(
|
||||||
|
base={
|
||||||
|
"egress": {
|
||||||
|
"routes": [{"host": "a.example.com"}],
|
||||||
|
"log": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
child={"extends": "base", "egress": {"log": 2}},
|
||||||
|
)
|
||||||
|
child = m.bottles["child"].egress
|
||||||
|
self.assertEqual(["a.example.com"], [r.Host for r in child.routes])
|
||||||
|
self.assertEqual(2, child.Log)
|
||||||
|
|
||||||
|
def test_child_egress_routes_inherit_parent_log_when_omitted(self):
|
||||||
|
m = _build(
|
||||||
|
base={
|
||||||
|
"egress": {
|
||||||
|
"routes": [{"host": "a.example.com"}],
|
||||||
|
"log": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
child={
|
||||||
|
"extends": "base",
|
||||||
|
"egress": {"routes": [{"host": "b.example.com"}]},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
child = m.bottles["child"].egress
|
||||||
|
self.assertEqual(
|
||||||
|
["a.example.com", "b.example.com"],
|
||||||
|
[r.Host for r in child.routes],
|
||||||
|
)
|
||||||
|
self.assertEqual(1, child.Log)
|
||||||
|
|
||||||
|
def test_duplicate_host_across_parent_and_child_dies(self):
|
||||||
|
msg = _error_message(
|
||||||
|
_build,
|
||||||
|
base={"egress": {"routes": [{"host": "a.example.com"}]}},
|
||||||
|
child={
|
||||||
|
"extends": "base",
|
||||||
|
"egress": {"routes": [{"host": "A.EXAMPLE.COM"}]},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertIn("duplicate host", msg)
|
||||||
|
self.assertIn("A.EXAMPLE.COM", msg)
|
||||||
|
|
||||||
|
|
||||||
class TestExtendsGitUserOverlay(unittest.TestCase):
|
class TestExtendsGitUserOverlay(unittest.TestCase):
|
||||||
"""git-gate.user: per-field overlay. Each non-empty field on child
|
"""git-gate.user: per-field overlay. Each non-empty field on child
|
||||||
|
|||||||
Reference in New Issue
Block a user