Test that source defines no Traefik labels
This commit is contained in:
@@ -16,16 +16,6 @@ COMPOSE_FILE = ROOT / "compose.yaml"
|
|||||||
DOCKERFILE = ROOT / "Dockerfile"
|
DOCKERFILE = ROOT / "Dockerfile"
|
||||||
COMPOSE_BIN = os.environ.get("COMPOSE_BIN", "/opt/data/tmp/docker-compose-birweb")
|
COMPOSE_BIN = os.environ.get("COMPOSE_BIN", "/opt/data/tmp/docker-compose-birweb")
|
||||||
SYNTHETIC_RESOURCE_UUID = "contract-test-resource"
|
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"
|
DOCKERFILE_SHA256 = "748bd362b43475779d241074a1955ae880967c923a7f68c693cb1c67f8b242dd"
|
||||||
BASE_IMAGE = (
|
BASE_IMAGE = (
|
||||||
"nginxinc/nginx-unprivileged:1.27-alpine@"
|
"nginxinc/nginx-unprivileged:1.27-alpine@"
|
||||||
@@ -176,44 +166,33 @@ class ComposeContract(unittest.TestCase):
|
|||||||
self.assertGreaterEqual(memory, 16 * 1024**2)
|
self.assertGreaterEqual(memory, 16 * 1024**2)
|
||||||
self.assertLessEqual(memory, 512 * 1024**2)
|
self.assertLessEqual(memory, 512 * 1024**2)
|
||||||
|
|
||||||
def test_rendered_traefik_router_priorities_are_exact(self) -> None:
|
def test_source_defines_no_traefik_labels(self) -> None:
|
||||||
rendered_labels = self.service().get("labels")
|
source_label_names = {
|
||||||
self.assertIsInstance(rendered_labels, dict)
|
match.group(1).strip("'\"")
|
||||||
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)
|
|
||||||
for match in re.finditer(
|
for match in re.finditer(
|
||||||
r'^\s*-\s*["\']?(traefik\.(?:http\.)?[^"\']+?)["\']?\s*$',
|
r'^\s*-\s*([^\s=]+)(?:=.*)?$',
|
||||||
self.source,
|
self.source,
|
||||||
re.MULTILINE,
|
re.IGNORECASE | re.MULTILINE,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
required_interpolation = re.compile(
|
source_label_names.update(
|
||||||
r"\$\{COOLIFY_RESOURCE_UUID:\?[^}\r\n]+\}"
|
key
|
||||||
)
|
|
||||||
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)
|
|
||||||
for _, key, line in source_mapping_keys(self.source)
|
for _, key, line in source_mapping_keys(self.source)
|
||||||
if key.startswith("traefik.")
|
)
|
||||||
]
|
traefik_labels = sorted(
|
||||||
self.assertEqual(mapping_labels, [], "Traefik labels must use Compose list syntax")
|
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 = os.environ.copy()
|
||||||
missing_env.pop("COOLIFY_RESOURCE_UUID", None)
|
missing_env.pop("COOLIFY_RESOURCE_UUID", None)
|
||||||
empty_env = os.environ.copy()
|
empty_env = os.environ.copy()
|
||||||
@@ -222,12 +201,11 @@ class ComposeContract(unittest.TestCase):
|
|||||||
for case, env in (("missing", missing_env), ("empty", empty_env)):
|
for case, env in (("missing", missing_env), ("empty", empty_env)):
|
||||||
with self.subTest(case=case):
|
with self.subTest(case=case):
|
||||||
rendered = run(self.render_command, env=env)
|
rendered = run(self.render_command, env=env)
|
||||||
self.assertNotEqual(
|
self.assertEqual(
|
||||||
rendered.returncode,
|
rendered.returncode,
|
||||||
0,
|
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:
|
def test_no_secrets_bind_mounts_or_forbidden_source_keys(self) -> None:
|
||||||
service = self.service()
|
service = self.service()
|
||||||
|
|||||||
Reference in New Issue
Block a user