Compare commits

..

2 Commits

Author SHA1 Message Date
BirSite Automation
f094dab182 Remove source Traefik labels 2026-07-19 18:05:39 +00:00
BirSite Automation
d2f763c1e5 Test that source defines no Traefik labels 2026-07-19 18:04:49 +00:00
2 changed files with 23 additions and 48 deletions

View File

@@ -11,9 +11,6 @@ services:
- no-new-privileges:true
expose:
- "8080"
labels:
- traefik.http.routers.http-0-${COOLIFY_RESOURCE_UUID:?required}-site.priority=500
- traefik.http.routers.https-0-${COOLIFY_RESOURCE_UUID:?required}-site.priority=500
tmpfs:
- /tmp:rw,noexec,nosuid,size=16m
- /var/cache/nginx:rw,noexec,nosuid,size=16m

View File

@@ -16,16 +16,6 @@ COMPOSE_FILE = ROOT / "compose.yaml"
DOCKERFILE = ROOT / "Dockerfile"
COMPOSE_BIN = os.environ.get("COMPOSE_BIN", "/opt/data/tmp/docker-compose-birweb")
SYNTHETIC_RESOURCE_UUID = "contract-test-resource"
ROUTER_PRIORITY_LABELS = {
f"traefik.http.routers.http-0-{SYNTHETIC_RESOURCE_UUID}-site.priority": "500",
f"traefik.http.routers.https-0-{SYNTHETIC_RESOURCE_UUID}-site.priority": "500",
}
SOURCE_ROUTER_PRIORITY_TEMPLATES = {
"traefik.http.routers.http-0-${UUID}-site.priority=500",
"traefik.http.routers.https-0-${UUID}-site.priority=500",
}
DOCKERFILE_SHA256 = "748bd362b43475779d241074a1955ae880967c923a7f68c693cb1c67f8b242dd"
BASE_IMAGE = (
"nginxinc/nginx-unprivileged:1.27-alpine@"
@@ -176,44 +166,33 @@ class ComposeContract(unittest.TestCase):
self.assertGreaterEqual(memory, 16 * 1024**2)
self.assertLessEqual(memory, 512 * 1024**2)
def test_rendered_traefik_router_priorities_are_exact(self) -> None:
rendered_labels = self.service().get("labels")
self.assertIsInstance(rendered_labels, dict)
labels = rendered_labels if isinstance(rendered_labels, dict) else {}
self.assertEqual(labels, ROUTER_PRIORITY_LABELS)
self.assertTrue(all(isinstance(value, str) for value in labels.values()))
def test_priority_labels_use_source_list_syntax_and_uuid_interpolation(self) -> None:
source_label_items = {
match.group(1)
def test_source_defines_no_traefik_labels(self) -> None:
source_label_names = {
match.group(1).strip("'\"")
for match in re.finditer(
r'^\s*-\s*["\']?(traefik\.(?:http\.)?[^"\']+?)["\']?\s*$',
r'^\s*-\s*([^\s=]+)(?:=.*)?$',
self.source,
re.MULTILINE,
re.IGNORECASE | re.MULTILINE,
)
}
required_interpolation = re.compile(
r"\$\{COOLIFY_RESOURCE_UUID:\?[^}\r\n]+\}"
)
normalized_items = {
required_interpolation.sub("${UUID}", item)
for item in source_label_items
}
self.assertEqual(normalized_items, SOURCE_ROUTER_PRIORITY_TEMPLATES)
self.assertTrue(
all(required_interpolation.search(item) for item in source_label_items),
"each Traefik label must use required COOLIFY_RESOURCE_UUID interpolation",
)
self.assertNotIn(SYNTHETIC_RESOURCE_UUID, self.source)
mapping_labels = [
(key, line)
source_label_names.update(
key
for _, key, line in source_mapping_keys(self.source)
if key.startswith("traefik.")
]
self.assertEqual(mapping_labels, [], "Traefik labels must use Compose list syntax")
)
traefik_labels = sorted(
name
for name in source_label_names
if re.search(r"(?:^|[._-])traefik(?:[._-]|$)", name, re.IGNORECASE)
)
# Interpolating a label key is unsafe: Coolify preprocesses list labels
# into a mapping before Compose interpolation, leaving that key literal.
self.assertEqual(
traefik_labels,
[],
f"source Traefik labels are forbidden; Coolify owns them: {traefik_labels}",
)
def test_resource_uuid_interpolation_fails_closed_when_missing_or_empty(self) -> None:
def test_compose_renders_without_resource_uuid(self) -> None:
missing_env = os.environ.copy()
missing_env.pop("COOLIFY_RESOURCE_UUID", None)
empty_env = os.environ.copy()
@@ -222,12 +201,11 @@ class ComposeContract(unittest.TestCase):
for case, env in (("missing", missing_env), ("empty", empty_env)):
with self.subTest(case=case):
rendered = run(self.render_command, env=env)
self.assertNotEqual(
self.assertEqual(
rendered.returncode,
0,
f"Compose config accepted a {case} COOLIFY_RESOURCE_UUID",
f"Compose config rejected a {case} COOLIFY_RESOURCE_UUID:\n{rendered.stderr}",
)
self.assertIn("COOLIFY_RESOURCE_UUID", rendered.stderr)
def test_no_secrets_bind_mounts_or_forbidden_source_keys(self) -> None:
service = self.service()