From 2460d8db5d1a68462ba313004a84a1bac753fba1 Mon Sep 17 00:00:00 2001 From: Filippo Pacifici Date: Mon, 31 Aug 2026 15:33:40 -0700 Subject: [PATCH] Avoid naming overflow in k8s --- .../sentry_streams_k8s/consumer_builder.py | 8 ++- .../tests/test_pipeline_step.py | 56 +++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/sentry_streams_k8s/sentry_streams_k8s/consumer_builder.py b/sentry_streams_k8s/sentry_streams_k8s/consumer_builder.py index 81d916c0..356d6587 100644 --- a/sentry_streams_k8s/sentry_streams_k8s/consumer_builder.py +++ b/sentry_streams_k8s/sentry_streams_k8s/consumer_builder.py @@ -58,7 +58,7 @@ def load_base_template(file_name: str) -> dict[str, Any]: return cast(dict[str, Any], yaml.safe_load(template_content.read_text())) -def make_k8s_name(name: str) -> str: +def make_k8s_name(name: str, limit: int | None = None) -> str: """ Generate a valid Kubernetes name from a string. @@ -74,6 +74,10 @@ def make_k8s_name(name: str) -> str: name = name.replace(".", "-").replace("_", "-").lower() name = re.sub(r"[^a-z0-9-]", "", name) name = name.strip("-") + if limit is not None: + # Truncation can land on a hyphen (e.g. a dotted module path). Kubernetes + # label values must start and end with an alphanumeric character. + return name[:limit].rstrip("-") return name @@ -292,7 +296,7 @@ def _build_merged_pipeline_deployment( def _pipeline_labels(spec: ConsumerSpec) -> dict[str, str]: return { - "pipeline-app": make_k8s_name(spec.pipeline_module), + "pipeline-app": make_k8s_name(spec.pipeline_module, limit=63), "pipeline": make_k8s_name(spec.pipeline_name), "service": make_k8s_name(spec.service_name), } diff --git a/sentry_streams_k8s/tests/test_pipeline_step.py b/sentry_streams_k8s/tests/test_pipeline_step.py index 2e4b519f..fe750984 100644 --- a/sentry_streams_k8s/tests/test_pipeline_step.py +++ b/sentry_streams_k8s/tests/test_pipeline_step.py @@ -27,6 +27,62 @@ def test_make_k8s_name() -> None: # Test with special characters (should be removed) assert make_k8s_name("my@module.sub#module") == "mymodule-submodule" + long_name = "a" * 80 + assert make_k8s_name(long_name, limit=63) == "a" * 63 + assert make_k8s_name(long_name) == long_name + + # Truncation must not leave a trailing hyphen (invalid K8s label value). + assert make_k8s_name("abc.def", limit=4) == "abc" + assert not make_k8s_name("a.b.c.d.e.f.g.h", limit=8).endswith("-") + + +def test_pipeline_app_label_truncated_to_63_chars() -> None: + """Kubernetes label values are limited to 63 characters.""" + # Repeating "ab." sanitizes to "ab-ab-..." so a 63-char slice would end in "-". + long_module = "ab." * 30 + sanitized = make_k8s_name(long_module) + assert len(sanitized) > 63 + assert sanitized[:63].endswith("-") + + context: dict[str, Any] = { + "service_name": "my-service", + "pipeline_name": "profiles", + "deployment_template": {}, + "container_template": {}, + "pipeline_config": { + "env": {}, + "pipeline": { + "segments": [ + { + "steps_config": { + "myinput": { + "starts_segment": True, + "bootstrap_servers": ["127.0.0.1:9092"], + } + } + } + ] + }, + }, + "pipeline_module": long_module, + "image_name": "my-image:latest", + "cpu_per_process": 1000, + "memory_per_process": 512, + "segment_id": 0, + "replicas": 1, + } + + result = PipelineStep().run(context) + expected = make_k8s_name(long_module, limit=63) + assert not expected.endswith("-") + assert len(expected) <= 63 + assert result["deployment"]["metadata"]["labels"]["pipeline-app"] == expected + assert result["deployment"]["spec"]["selector"]["matchLabels"]["pipeline-app"] == expected + assert ( + result["deployment"]["spec"]["template"]["metadata"]["labels"]["pipeline-app"] == expected + ) + assert result["configmap"]["metadata"]["labels"]["pipeline-app"] == expected + def test_parse_context() -> None: