refactor(meta): split database PORT_BAND into mariadb + postgres + disjoint lint

The single `local.database` PORT_BAND covered both 3306 (MariaDB)
and 5432 (PostgreSQL), which let any port in 3306-5432 pass band
membership without distinguishing the engine. Replace with two
explicit single-port bands so the (scope, category) tuple
identifies the engine unambiguously:

- group_vars/all/08_networks.yml: `local.database` ->
  `local.mariadb` (3306-3306) and `local.postgres` (5432-5432).
- roles/svc-db-mariadb/meta/services.yml: ports.local.database
  -> ports.local.mariadb.
- roles/svc-db-postgres/meta/services.yml: ports.local.database
  -> ports.local.postgres.
- roles/svc-db-{mariadb,postgres}/vars/main.yml: lookups follow
  the new path.

Also add `TestPortBandsDisjoint.test_no_overlapping_bands_in_same_scope`
in tests/lint/repository/test_role_meta_layout.py: rejects any
two PORT_BANDS entries within the same scope whose ranges
intersect, with a precise overlap range in the failure message.
Cross-scope overlaps remain intentional (different bind contexts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 4.7 committed 2026-05-06 19:56:36 +02:00
1 parent d97b16e9f1
commit 1e94b14dd4
6 files changed
+62 -5

No files matched your search

+4 -1
View File
@@ -54,8 +54,11 @@ PORT_BANDS:
oauth2:
start: 16480
end: 16519
database:
mariadb:
start: 3306
end: 3306
postgres:
start: 5432
end: 5432
ldap:
start: 389
+1 -1
View File
@@ -13,5 +13,5 @@ mariadb:
pids_limit: 1024
ports:
local:
database: 3306
mariadb: 3306
lifecycle: beta
+1 -1
View File
@@ -9,7 +9,7 @@ MARIADB_VOLUME: "{{ lookup('config', application_id, 'volumes.data') }}"
MARIADB_IMAGE: "{{ lookup('config', application_id, 'services.' ~ entity_name ~ '.image') }}"
MARIADB_VERSION: "{{ lookup('config', application_id, 'services.' ~ entity_name ~ '.version') }}"
MARIADB_NAME: "{{ lookup('config', application_id, 'services.' ~ entity_name ~ '.name') }}"
MARIADB_PORT: "{{ database_port | default(lookup('config', application_id, 'services.mariadb.ports.local.database')) }}"
MARIADB_PORT: "{{ database_port | default(lookup('config', application_id, 'services.mariadb.ports.local.mariadb')) }}"
MARIADB_HOST: "127.0.0.1"
MARIADB_CUSTOM_IMAGE: "{{ entity_name }}_custom:{{ MARIADB_VERSION }}"
MARIADB_HEALTHCHECK_DB: "{{ SOFTWARE_DOMAIN | regex_replace('[^a-z0-9]+', '_') }}_healthcheck"
+1 -1
View File
@@ -13,5 +13,5 @@ postgres:
pids_limit: 1024
ports:
local:
database: 5432
postgres: 5432
lifecycle: beta
+1 -1
View File
@@ -14,7 +14,7 @@ POSTGRES_VERSION_MAJOR: "{{ POSTGRES_VERSION | regex_replace('
POSTGRES_NETWORK_NAME: "{{ application_id | get_entity_name }}"
POSTGRES_SUBNET: "{{ lookup('config', 'svc-db-postgres', 'server.networks.local.subnet') }}"
POSTGRES_PASSWORD: "{{ lookup('config', application_id, 'credentials.POSTGRES_PASSWORD') }}"
POSTGRES_PORT: "{{ database_port | default(lookup('config', application_id, 'services.postgres.ports.local.database')) }}"
POSTGRES_PORT: "{{ database_port | default(lookup('config', application_id, 'services.postgres.ports.local.postgres')) }}"
POSTGRES_EXPOSE_LOCAL: True # Exposes the db to localhost, almost everytime neccessary
POSTGRES_CUSTOM_IMAGE: "{{ entity_name }}_custom:{{ POSTGRES_VERSION }}"
POSTGRES_LOCAL_HOST: "127.0.0.1"
@@ -337,6 +337,60 @@ class TestPortBandMembership(unittest.TestCase):
)
class TestPortBandsDisjoint(unittest.TestCase):
"""PORT_BANDS within a single scope MUST be pairwise disjoint, otherwise
a port could legitimately belong to two categories at once and the
suggester / band-membership lint would silently route it to the
wrong category. Cross-scope overlaps (e.g. local.http=8001-8099 and
public.https=...) are intentional and not checked here."""
def test_no_overlapping_bands_in_same_scope(self):
from utils.meta.port_bands import load_port_bands
offenders: list[str] = []
for scope, scope_block in (load_port_bands() or {}).items():
if not isinstance(scope_block, dict):
continue
ranges: list[tuple[str, int, int]] = []
for category, entry in scope_block.items():
if not isinstance(entry, dict):
continue
start = entry.get("start")
end = entry.get("end")
if not isinstance(start, int) or not isinstance(end, int):
continue
if start > end:
offenders.append(
f"PORT_BANDS.{scope}.{category}: start ({start}) > end ({end})"
)
continue
ranges.append((category, start, end))
ranges.sort(key=lambda r: (r[1], r[2]))
for i in range(len(ranges)):
cat_a, start_a, end_a = ranges[i]
for j in range(i + 1, len(ranges)):
cat_b, start_b, end_b = ranges[j]
if start_b > end_a:
break
overlap_start = max(start_a, start_b)
overlap_end = min(end_a, end_b)
offenders.append(
f"PORT_BANDS.{scope}.{cat_a}={start_a}-{end_a} overlaps "
f"PORT_BANDS.{scope}.{cat_b}={start_b}-{end_b} on "
f"{overlap_start}-{overlap_end}"
)
if offenders:
self.fail(
"PORT_BANDS overlap detected (req-009):\n"
+ "\n".join(f" - {o}" for o in offenders)
+ "\n\nFix: shrink one of the overlapping ranges in "
+ "group_vars/all/08_networks.yml so each port belongs to "
+ "at most one (scope, category) pair."
)
class TestNoComposeWrapperInVariants(unittest.TestCase):
"""Per req-008 the file root of meta/services.yml IS the services map.
Variant overrides in meta/variants.yml MUST follow the same shape: top