diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/clients/pipeline/k8s/K8sPipelineClient.java b/openmetadata-service/src/main/java/org/openmetadata/service/clients/pipeline/k8s/K8sPipelineClient.java index 4671b1677efe..372681c8893a 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/clients/pipeline/k8s/K8sPipelineClient.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/clients/pipeline/k8s/K8sPipelineClient.java @@ -564,7 +564,8 @@ public PipelineServiceClientResponse runPipeline( String pipelineName = sanitizeName(ingestionPipeline.getName()); String runId = UUID.randomUUID().toString(); String correlationId = runId.substring(0, 8); - String jobName = JOB_PREFIX + pipelineName + "-" + correlationId; + String jobSuffix = "-" + correlationId; + String jobName = buildKubernetesName(JOB_PREFIX, ingestionPipeline.getName(), jobSuffix); MDC.put(MDC_CORRELATION_ID, correlationId); MDC.put(MDC_PIPELINE_NAME, pipelineName); @@ -1318,8 +1319,7 @@ CronOMJob buildCronOMJob(IngestionPipeline pipeline) { } private V1JobSpec buildJobSpecForCronJob(IngestionPipeline pipeline) { - String pipelineName = sanitizeName(pipeline.getName()); - String configMapName = CONFIG_MAP_PREFIX + pipelineName; + String configMapName = buildKubernetesName(CONFIG_MAP_PREFIX, pipeline.getName()); return new V1JobSpec() .backoffLimit(k8sConfig.getBackoffLimit()) @@ -1995,7 +1995,7 @@ private Map buildLabels(IngestionPipeline pipeline, String runId labels.put(LABEL_APP, LABEL_VALUE_OPENMETADATA); labels.put(LABEL_COMPONENT, LABEL_VALUE_INGESTION); labels.put(LABEL_MANAGED_BY, LABEL_VALUE_OPENMETADATA); - labels.put(LABEL_PIPELINE, sanitizeName(pipeline.getName())); + labels.put(LABEL_PIPELINE, buildKubernetesName("", pipeline.getName())); labels.put(LABEL_PIPELINE_TYPE, pipeline.getPipelineType().toString().toLowerCase()); if (runId != null && !SCHEDULED_RUN_ID.equals(runId)) { @@ -2214,17 +2214,13 @@ private String convertToCronSchedule(String airflowSchedule) { String sanitizeName(String name) { // K8s names must be lowercase, alphanumeric, or '-' - // Must start with alphanumeric, max 63 chars + // Must start with alphanumeric. Truncation is handled by buildKubernetesName. String sanitized = name.toLowerCase() .replaceAll("[^a-z0-9-]", "-") .replaceAll("-+", "-") .replaceAll("^-+|-+$", ""); - if (sanitized.length() > 53) { // Leave room for prefixes - sanitized = sanitized.substring(0, 53).replaceAll("-+$", ""); - } - if (!sanitized.matches("^[a-z0-9].*")) { sanitized = "p-" + sanitized; } @@ -2238,12 +2234,17 @@ private String buildKubernetesName(String prefix, String name) { private String buildKubernetesName(String prefix, String name, String suffix) { String sanitized = sanitizeName(name); + // Kubernetes names and label values have a 63-character limit. int maxBaseLength = KUBERNETES_NAME_MAX_LENGTH - prefix.length() - suffix.length(); if (maxBaseLength < 1) { - throw new IllegalArgumentException("Kubernetes name prefix/suffix leaves no room for base"); + throw new IllegalArgumentException( + String.format( + "Kubernetes name prefix '%s' and suffix '%s' leave no room for base name", + prefix, suffix)); } if (sanitized.length() > maxBaseLength) { + // Truncate and remove trailing dashes to keep it a valid K8s name sanitized = sanitized.substring(0, maxBaseLength).replaceAll("-+$", ""); } diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/clients/pipeline/k8s/K8sPipelineClientTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/clients/pipeline/k8s/K8sPipelineClientTest.java index 06e873b55282..d583794a9666 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/clients/pipeline/k8s/K8sPipelineClientTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/clients/pipeline/k8s/K8sPipelineClientTest.java @@ -1705,6 +1705,44 @@ private static V1Pod pod( .spec(new V1PodSpec().containers(containers)); } + @Test + void testBuildLabelsTruncatesLongPipelineNameToKubernetesLimit() { + // Tests P0: Label values must be <= 63 characters + String longName = "a".repeat(95); + IngestionPipeline pipeline = createTestPipeline(longName, null); + + Map labels = + assertDoesNotThrow(() -> invokePrivate(client, "buildLabels", + new Class[] {IngestionPipeline.class, String.class}, pipeline, "run-123")); + + String pipelineLabel = labels.get("app.kubernetes.io/pipeline"); + assertNotNull(pipelineLabel); + assertTrue( + pipelineLabel.length() <= 63, + String.format("Label length %d exceeds Kubernetes limit of 63", pipelineLabel.length())); + assertTrue(pipelineLabel.startsWith("aaaaaaaa")); + } + + @Test + void testJobNameTruncationWithLongPipelineName() { + // Tests P0: Job names (prefix + name + suffix) must be <= 63 characters + String longName = "very-long-pipeline-name-that-exceeds-the-normal-limits-of-kubernetes-resource-naming"; + IngestionPipeline pipeline = createTestPipeline(longName, null); + String runId = "12345678-1234-1234-1234-1234567890ab"; + + // JOB_PREFIX (7) + name + suffix (9) + String jobName = + assertDoesNotThrow(() -> invokePrivate(client, "buildKubernetesName", + new Class[] {String.class, String.class, String.class}, "om-job-", pipeline.getName(), "-" + runId.substring(0, 8))); + + assertNotNull(jobName); + assertTrue( + jobName.length() <= 63, + String.format("Job name length %d exceeds Kubernetes limit of 63: %s", jobName.length(), jobName)); + assertTrue(jobName.startsWith("om-job-")); + assertTrue(jobName.endsWith("-12345678")); + } + private static Workflow createTestWorkflow(String name) { Workflow workflow = new Workflow(); workflow.setId(UUID.randomUUID());