diff --git a/chart/newsfragments/53190.feature.rst b/chart/newsfragments/53190.feature.rst new file mode 100644 index 0000000000000..bfa28f6e2e9a4 --- /dev/null +++ b/chart/newsfragments/53190.feature.rst @@ -0,0 +1 @@ +Allow pod labels to be configured separately from object labels with ``podLabels``, for ``apiServer``, ``scheduler``, ``workers.celery``, ``triggerer``, ``dagProcessor``, ``flower``, ``statsd``, ``pgbouncer``, ``redis`` and ``otelCollector``. Leaving ``podLabels`` unset keeps the existing behaviour. diff --git a/chart/templates/api-server/api-server-deployment.yaml b/chart/templates/api-server/api-server-deployment.yaml index bc02135ae2b04..b091026ab8b1c 100644 --- a/chart/templates/api-server/api-server-deployment.yaml +++ b/chart/templates/api-server/api-server-deployment.yaml @@ -30,6 +30,12 @@ {{- $containerSecurityContext := include "containerSecurityContext" (list .Values.apiServer .Values) }} {{- $containerSecurityContextWaitForMigrations := include "containerSecurityContext" (list .Values.apiServer.waitForMigrations .Values) }} {{- $containerLifecycleHooks := or .Values.apiServer.containerLifecycleHooks .Values.containerLifecycleHooks }} +{{- $deploymentLabels := .Values.labels }} +{{- $podLabels := .Values.apiServer.labels }} +{{- if ne .Values.apiServer.podLabels nil }} + {{- $deploymentLabels = mustMerge (deepCopy .Values.apiServer.labels) .Values.labels }} + {{- $podLabels = .Values.apiServer.podLabels }} +{{- end }} apiVersion: apps/v1 kind: Deployment metadata: @@ -40,7 +46,7 @@ metadata: release: {{ .Release.Name }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" heritage: {{ .Release.Service }} - {{- with .Values.labels }} + {{- with $deploymentLabels }} {{- toYaml . | nindent 4 }} {{- end }} {{- if .Values.apiServer.annotations }} @@ -80,8 +86,8 @@ spec: tier: airflow component: api-server release: {{ .Release.Name }} - {{- if or .Values.labels .Values.apiServer.labels }} - {{- mustMerge .Values.apiServer.labels .Values.labels | toYaml | nindent 8 }} + {{- if or .Values.labels $podLabels }} + {{- mustMerge (deepCopy $podLabels) .Values.labels | toYaml | nindent 8 }} {{- end }} annotations: checksum/metadata-secret: {{ include (print $.Template.BasePath "/secrets/metadata-connection-secret.yaml") . | sha256sum }} diff --git a/chart/templates/dag-processor/dag-processor-deployment.yaml b/chart/templates/dag-processor/dag-processor-deployment.yaml index d3596527c7765..3822a4a43e4f2 100644 --- a/chart/templates/dag-processor/dag-processor-deployment.yaml +++ b/chart/templates/dag-processor/dag-processor-deployment.yaml @@ -31,6 +31,12 @@ {{- $containerSecurityContextLogGroomerSidecar := include "containerSecurityContext" (list .Values.dagProcessor.logGroomerSidecar .Values) }} {{- $containerSecurityContextWaitForMigrations := include "containerSecurityContext" (list .Values.dagProcessor.waitForMigrations .Values) }} {{- $containerLifecycleHooks := or .Values.dagProcessor.containerLifecycleHooks .Values.containerLifecycleHooks }} +{{- $deploymentLabels := .Values.labels }} +{{- $podLabels := .Values.dagProcessor.labels }} +{{- if ne .Values.dagProcessor.podLabels nil }} + {{- $deploymentLabels = mustMerge (deepCopy .Values.dagProcessor.labels) .Values.labels }} + {{- $podLabels = .Values.dagProcessor.podLabels }} +{{- end }} apiVersion: apps/v1 kind: Deployment metadata: @@ -41,7 +47,7 @@ metadata: release: {{ .Release.Name }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" heritage: {{ .Release.Service }} - {{- with .Values.labels }} + {{- with $deploymentLabels }} {{- toYaml . | nindent 4 }} {{- end }} {{- if .Values.dagProcessor.annotations }} @@ -66,8 +72,8 @@ spec: tier: airflow component: dag-processor release: {{ .Release.Name }} - {{- if or .Values.labels .Values.dagProcessor.labels }} - {{- mustMerge .Values.dagProcessor.labels .Values.labels | toYaml | nindent 8 }} + {{- if or .Values.labels $podLabels }} + {{- mustMerge (deepCopy $podLabels) .Values.labels | toYaml | nindent 8 }} {{- end }} annotations: checksum/metadata-secret: {{ include (print $.Template.BasePath "/secrets/metadata-connection-secret.yaml") . | sha256sum }} diff --git a/chart/templates/flower/flower-deployment.yaml b/chart/templates/flower/flower-deployment.yaml index aa6cd31c0ec1f..cbfb03c2cf7d0 100644 --- a/chart/templates/flower/flower-deployment.yaml +++ b/chart/templates/flower/flower-deployment.yaml @@ -29,6 +29,12 @@ {{- $securityContext := include "airflowPodSecurityContext" (list .Values.flower .Values) }} {{- $containerSecurityContext := include "containerSecurityContext" (list .Values.flower .Values) }} {{- $containerLifecycleHooks := or .Values.flower.containerLifecycleHooks .Values.containerLifecycleHooks }} +{{- $deploymentLabels := .Values.labels }} +{{- $podLabels := .Values.flower.labels }} +{{- if ne .Values.flower.podLabels nil }} + {{- $deploymentLabels = mustMerge (deepCopy .Values.flower.labels) .Values.labels }} + {{- $podLabels = .Values.flower.podLabels }} +{{- end }} apiVersion: apps/v1 kind: Deployment metadata: @@ -39,7 +45,7 @@ metadata: release: {{ .Release.Name }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" heritage: {{ .Release.Service }} - {{- with .Values.labels }} + {{- with $deploymentLabels }} {{- toYaml . | nindent 4 }} {{- end }} {{- if .Values.flower.annotations }} @@ -61,8 +67,8 @@ spec: tier: airflow component: flower release: {{ .Release.Name }} - {{- if or .Values.labels .Values.flower.labels }} - {{- mustMerge .Values.flower.labels .Values.labels | toYaml | nindent 8 }} + {{- if or .Values.labels $podLabels }} + {{- mustMerge (deepCopy $podLabels) .Values.labels | toYaml | nindent 8 }} {{- end }} annotations: checksum/airflow-config: {{ include (print $.Template.BasePath "/configmaps/configmap.yaml") . | sha256sum }} diff --git a/chart/templates/otel-collector/otel-collector-deployment.yaml b/chart/templates/otel-collector/otel-collector-deployment.yaml index abbcbc8bd9bf7..99dd1705116fd 100644 --- a/chart/templates/otel-collector/otel-collector-deployment.yaml +++ b/chart/templates/otel-collector/otel-collector-deployment.yaml @@ -26,6 +26,10 @@ {{- $tolerations := or .Values.otelCollector.tolerations .Values.tolerations }} {{- $topologySpreadConstraints := or .Values.otelCollector.topologySpreadConstraints .Values.topologySpreadConstraints }} {{- $revisionHistoryLimit := include "airflow.revisionHistoryLimit" (list .Values.otelCollector.revisionHistoryLimit .Values.revisionHistoryLimit) }} +{{- $podLabels := .Values.otelCollector.labels }} +{{- if ne .Values.otelCollector.podLabels nil }} + {{- $podLabels = .Values.otelCollector.podLabels }} +{{- end }} apiVersion: apps/v1 kind: Deployment metadata: @@ -58,8 +62,8 @@ spec: tier: airflow component: otel-collector release: {{ .Release.Name }} - {{- if or .Values.labels .Values.otelCollector.labels }} - {{- mustMerge .Values.otelCollector.labels .Values.labels | toYaml | nindent 8 }} + {{- if or .Values.labels $podLabels }} + {{- mustMerge (deepCopy $podLabels) .Values.labels | toYaml | nindent 8 }} {{- end }} annotations: checksum/otel-collector-config: {{ include (print $.Template.BasePath "/configmaps/otel-collector-configmap.yaml") . | sha256sum }} diff --git a/chart/templates/pgbouncer/pgbouncer-deployment.yaml b/chart/templates/pgbouncer/pgbouncer-deployment.yaml index b1aad5e1d0e08..6aa63dbab6265 100644 --- a/chart/templates/pgbouncer/pgbouncer-deployment.yaml +++ b/chart/templates/pgbouncer/pgbouncer-deployment.yaml @@ -31,6 +31,12 @@ {{- $containerSecurityContextMetricsExporter := include "externalContainerSecurityContext" .Values.pgbouncer.metricsExporterSidecar }} {{- $containerLifecycleHooks := .Values.pgbouncer.containerLifecycleHooks }} {{- $containerLifecycleHooksMetricsExporter := .Values.pgbouncer.metricsExporterSidecar.containerLifecycleHooks }} +{{- $deploymentLabels := .Values.labels }} +{{- $podLabels := .Values.pgbouncer.labels }} +{{- if ne .Values.pgbouncer.podLabels nil }} + {{- $deploymentLabels = mustMerge (deepCopy .Values.pgbouncer.labels) .Values.labels }} + {{- $podLabels = .Values.pgbouncer.podLabels }} +{{- end }} apiVersion: apps/v1 kind: Deployment metadata: @@ -41,7 +47,7 @@ metadata: release: {{ .Release.Name }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" heritage: {{ .Release.Service }} - {{- with .Values.labels }} + {{- with $deploymentLabels }} {{- toYaml . | nindent 4 }} {{- end }} {{- if .Values.pgbouncer.annotations }} @@ -67,8 +73,8 @@ spec: tier: airflow component: pgbouncer release: {{ .Release.Name }} - {{- if or .Values.labels .Values.pgbouncer.labels }} - {{- mustMerge .Values.pgbouncer.labels .Values.labels | toYaml | nindent 8 }} + {{- if or .Values.labels $podLabels }} + {{- mustMerge (deepCopy $podLabels) .Values.labels | toYaml | nindent 8 }} {{- end }} annotations: checksum/pgbouncer-config-secret: {{ include (print $.Template.BasePath "/secrets/pgbouncer-config-secret.yaml") . | sha256sum }} diff --git a/chart/templates/redis/redis-statefulset.yaml b/chart/templates/redis/redis-statefulset.yaml index 361c229212a15..8139caec9f618 100644 --- a/chart/templates/redis/redis-statefulset.yaml +++ b/chart/templates/redis/redis-statefulset.yaml @@ -29,6 +29,12 @@ {{- $containerSecurityContext := include "externalContainerSecurityContext" .Values.redis }} {{- $containerLifecycleHooks := .Values.redis.containerLifecycleHooks }} {{- $persistence := .Values.redis.persistence.enabled }} +{{- $deploymentLabels := .Values.labels }} +{{- $podLabels := .Values.redis.labels }} +{{- if ne .Values.redis.podLabels nil }} + {{- $deploymentLabels = mustMerge (deepCopy .Values.redis.labels) .Values.labels }} + {{- $podLabels = .Values.redis.podLabels }} +{{- end }} apiVersion: apps/v1 kind: StatefulSet metadata: @@ -39,7 +45,7 @@ metadata: release: {{ .Release.Name }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" heritage: {{ .Release.Service }} - {{- with .Values.labels }} + {{- with $deploymentLabels }} {{- toYaml . | nindent 4 }} {{- end }} {{- if .Values.redis.annotations }} @@ -61,8 +67,8 @@ spec: tier: airflow component: redis release: {{ .Release.Name }} - {{- if or .Values.labels .Values.redis.labels }} - {{- mustMerge .Values.redis.labels .Values.labels | toYaml | nindent 8 }} + {{- if or .Values.labels $podLabels }} + {{- mustMerge (deepCopy $podLabels) .Values.labels | toYaml | nindent 8 }} {{- end }} {{- if or .Values.redis.safeToEvict .Values.redis.podAnnotations }} annotations: diff --git a/chart/templates/scheduler/scheduler-deployment.yaml b/chart/templates/scheduler/scheduler-deployment.yaml index 35749fdbc5ba2..d3bb2fc9aebb3 100644 --- a/chart/templates/scheduler/scheduler-deployment.yaml +++ b/chart/templates/scheduler/scheduler-deployment.yaml @@ -38,6 +38,12 @@ {{- $containerSecurityContextLogGroomerSidecar := include "containerSecurityContext" (list .Values.scheduler.logGroomerSidecar .Values) }} {{- $containerLifecycleHooks := or .Values.scheduler.containerLifecycleHooks .Values.containerLifecycleHooks }} {{- $containerLifecycleHooksLogGroomerSidecar := or .Values.scheduler.logGroomerSidecar.containerLifecycleHooks .Values.containerLifecycleHooks }} +{{- $deploymentLabels := .Values.labels }} +{{- $podLabels := .Values.scheduler.labels }} +{{- if ne .Values.scheduler.podLabels nil }} + {{- $deploymentLabels = mustMerge (deepCopy .Values.scheduler.labels) .Values.labels }} + {{- $podLabels = .Values.scheduler.podLabels }} +{{- end }} apiVersion: apps/v1 kind: {{ if $stateful }}StatefulSet{{ else }}Deployment{{ end }} metadata: @@ -49,7 +55,7 @@ metadata: chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" heritage: {{ .Release.Service }} executor: {{ .Values.executor | replace "," "-" | replace ":" "-" | trunc 63 | trimSuffix "-" | trimSuffix ":" | trimSuffix "_" | trimSuffix "." | quote }} - {{- with .Values.labels }} + {{- with $deploymentLabels }} {{- toYaml . | nindent 4 }} {{- end }} {{- if .Values.scheduler.annotations }} @@ -83,8 +89,8 @@ spec: tier: airflow component: scheduler release: {{ .Release.Name }} - {{- if or .Values.labels .Values.scheduler.labels }} - {{- mustMerge .Values.scheduler.labels .Values.labels | toYaml | nindent 8 }} + {{- if or .Values.labels $podLabels }} + {{- mustMerge (deepCopy $podLabels) .Values.labels | toYaml | nindent 8 }} {{- end }} annotations: checksum/metadata-secret: {{ include (print $.Template.BasePath "/secrets/metadata-connection-secret.yaml") . | sha256sum }} diff --git a/chart/templates/statsd/statsd-deployment.yaml b/chart/templates/statsd/statsd-deployment.yaml index bb47d7d0400fd..555d1b15dbbbc 100644 --- a/chart/templates/statsd/statsd-deployment.yaml +++ b/chart/templates/statsd/statsd-deployment.yaml @@ -29,6 +29,12 @@ {{- $securityContext := include "localPodSecurityContext" .Values.statsd }} {{- $containerSecurityContext := include "externalContainerSecurityContext" .Values.statsd }} {{- $containerLifecycleHooks := .Values.statsd.containerLifecycleHooks }} +{{- $deploymentLabels := .Values.labels }} +{{- $podLabels := .Values.statsd.labels }} +{{- if ne .Values.statsd.podLabels nil }} + {{- $deploymentLabels = mustMerge (deepCopy .Values.statsd.labels) .Values.labels }} + {{- $podLabels = .Values.statsd.podLabels }} +{{- end }} apiVersion: apps/v1 kind: Deployment metadata: @@ -39,7 +45,7 @@ metadata: release: {{ .Release.Name }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" heritage: {{ .Release.Service }} - {{- with .Values.labels }} + {{- with $deploymentLabels }} {{- toYaml . | nindent 4 }} {{- end }} {{- with .Values.statsd.annotations }} @@ -61,8 +67,8 @@ spec: tier: airflow component: statsd release: {{ .Release.Name }} - {{- if or .Values.labels .Values.statsd.labels }} - {{- mustMerge .Values.statsd.labels .Values.labels | toYaml | nindent 8 }} + {{- if or .Values.labels $podLabels }} + {{- mustMerge (deepCopy $podLabels) .Values.labels | toYaml | nindent 8 }} {{- end }} {{- if or .Values.statsd.extraMappings .Values.statsd.podAnnotations }} annotations: diff --git a/chart/templates/triggerer/triggerer-deployment.yaml b/chart/templates/triggerer/triggerer-deployment.yaml index 591153d3e054c..211f7d73cc237 100644 --- a/chart/templates/triggerer/triggerer-deployment.yaml +++ b/chart/templates/triggerer/triggerer-deployment.yaml @@ -34,6 +34,12 @@ {{- $containerSecurityContextLogGroomer := include "containerSecurityContext" (list .Values.triggerer.logGroomerSidecar .Values) }} {{- $containerLifecycleHooks := or .Values.triggerer.containerLifecycleHooks .Values.containerLifecycleHooks }} {{- $containerLifecycleHooksLogGroomerSidecar := or .Values.triggerer.logGroomerSidecar.containerLifecycleHooks .Values.containerLifecycleHooks }} +{{- $deploymentLabels := .Values.labels }} +{{- $podLabels := .Values.triggerer.labels }} +{{- if ne .Values.triggerer.podLabels nil }} + {{- $deploymentLabels = mustMerge (deepCopy .Values.triggerer.labels) .Values.labels }} + {{- $podLabels = .Values.triggerer.podLabels }} +{{- end }} apiVersion: apps/v1 kind: {{ if $persistence }}StatefulSet{{ else }}Deployment{{ end }} metadata: @@ -44,7 +50,7 @@ metadata: release: {{ .Release.Name }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" heritage: {{ .Release.Service }} - {{- with .Values.labels }} + {{- with $deploymentLabels }} {{- toYaml . | nindent 4 }} {{- end }} {{- if .Values.triggerer.annotations }} @@ -80,8 +86,8 @@ spec: tier: airflow component: triggerer release: {{ .Release.Name }} - {{- if or .Values.labels .Values.triggerer.labels }} - {{- mustMerge .Values.triggerer.labels .Values.labels | toYaml | nindent 8 }} + {{- if or .Values.labels $podLabels }} + {{- mustMerge (deepCopy $podLabels) .Values.labels | toYaml | nindent 8 }} {{- end }} annotations: checksum/metadata-secret: {{ include (print $.Template.BasePath "/secrets/metadata-connection-secret.yaml") . | sha256sum }} diff --git a/chart/templates/workers/worker-deployment.yaml b/chart/templates/workers/worker-deployment.yaml index d45a4b15485f3..e4dbac22c0a99 100644 --- a/chart/templates/workers/worker-deployment.yaml +++ b/chart/templates/workers/worker-deployment.yaml @@ -53,6 +53,12 @@ {{- $safeToEvict := dict "cluster-autoscaler.kubernetes.io/safe-to-evict" (.Values.workers.celery.safeToEvict | toString) }} {{- $podAnnotations := mergeOverwrite (deepCopy .Values.airflowPodAnnotations) $safeToEvict .Values.workers.celery.podAnnotations }} {{- $schedulerName := or .Values.workers.celery.schedulerName .Values.schedulerName }} +{{- $deploymentLabels := .Values.labels }} +{{- $podLabels := .Values.workers.celery.labels }} +{{- if ne .Values.workers.celery.podLabels nil }} + {{- $deploymentLabels = mustMerge (deepCopy .Values.workers.celery.labels) .Values.labels }} + {{- $podLabels = .Values.workers.celery.podLabels }} +{{- end }} apiVersion: apps/v1 kind: {{ if $persistence }}StatefulSet{{ else }}Deployment{{ end }} metadata: @@ -63,7 +69,7 @@ metadata: release: {{ .Release.Name }} chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" heritage: {{ .Release.Service }} - {{- with .Values.labels }} + {{- with $deploymentLabels }} {{- toYaml . | nindent 4 }} {{- end }} {{- if .Values.workers.celery.annotations }} @@ -108,8 +114,8 @@ spec: {{- if ne .Values.workers.celery.name "default" }} worker-set: {{ .Values.workers.celery.name }} {{- end }} - {{- if or .Values.labels .Values.workers.celery.labels }} - {{- mustMerge .Values.workers.celery.labels .Values.labels | toYaml | nindent 8 }} + {{- if or .Values.labels $podLabels }} + {{- mustMerge (deepCopy $podLabels) .Values.labels | toYaml | nindent 8 }} {{- end }} annotations: checksum/metadata-secret: {{ include (print $.Template.BasePath "/secrets/metadata-connection-secret.yaml") . | sha256sum }} diff --git a/chart/tests/helm_tests/airflow_aux/test_pod_labels.py b/chart/tests/helm_tests/airflow_aux/test_pod_labels.py new file mode 100644 index 0000000000000..b62a8af55dd9d --- /dev/null +++ b/chart/tests/helm_tests/airflow_aux/test_pod_labels.py @@ -0,0 +1,165 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from __future__ import annotations + +import pytest +from chart_utils.helm_template_generator import render_chart + +# Components whose workload object carries only the global labels until +# ``podLabels`` is set. ``dagProcessor`` has its own dedicated module in +# ``helm_tests/dagprocessor/test_labels_deployment.py``. +COMPONENTS = [ + ("apiServer", "templates/api-server/api-server-deployment.yaml", {}), + ("scheduler", "templates/scheduler/scheduler-deployment.yaml", {}), + ( + "workers.celery", + "templates/workers/worker-deployment.yaml", + {"executor": "CeleryExecutor"}, + ), + ("triggerer", "templates/triggerer/triggerer-deployment.yaml", {}), + ( + "flower", + "templates/flower/flower-deployment.yaml", + {"executor": "CeleryExecutor", "flower": {"enabled": True}}, + ), + ("statsd", "templates/statsd/statsd-deployment.yaml", {}), + ( + "pgbouncer", + "templates/pgbouncer/pgbouncer-deployment.yaml", + {"pgbouncer": {"enabled": True}}, + ), + ("redis", "templates/redis/redis-statefulset.yaml", {"executor": "CeleryExecutor"}), +] + +GLOBAL_LABELS = {"test_global_label": "test_global_label_value", "common_label": "global_value"} +COMPONENT_LABELS = { + "test_component_label": "test_component_label_value", + "common_label": "component_value", +} +POD_LABELS = {"test_pod_label": "test_pod_label_value", "common_label": "pod_value"} +OTEL_ENABLED = {"otelCollector": {"tracesEnabled": True}} + + +def _deep_merge(base: dict, overlay: dict) -> dict: + merged = dict(base) + for key, value in overlay.items(): + if isinstance(value, dict) and isinstance(merged.get(key), dict): + merged[key] = _deep_merge(merged[key], value) + else: + merged[key] = value + return merged + + +def _nest(values_key: str, payload: dict) -> dict: + """Expand a dotted values key, so ``workers.celery`` reaches the right table.""" + for part in reversed(values_key.split(".")): + payload = {part: payload} + return payload + + +def _render(values_key: str, show_only: str, extra_values: dict, component: dict) -> tuple[dict, dict]: + values = _deep_merge({"labels": GLOBAL_LABELS}, extra_values) + values = _deep_merge(values, _nest(values_key, component)) + docs = render_chart(values=values, show_only=[show_only]) + return docs[0]["metadata"]["labels"], docs[0]["spec"]["template"]["metadata"]["labels"] + + +@pytest.mark.parametrize(("values_key", "show_only", "extra_values"), COMPONENTS) +class TestPodLabels: + """Tests separating workload object labels from pod labels.""" + + def test_should_keep_component_labels_on_pods_when_pod_labels_are_unset( + self, values_key, show_only, extra_values + ): + object_labels, pod_labels = _render(values_key, show_only, extra_values, {"labels": COMPONENT_LABELS}) + + # Backward compatible: component labels reach pods, not the workload object. + assert object_labels["test_global_label"] == "test_global_label_value" + assert "test_component_label" not in object_labels + assert object_labels["common_label"] == "global_value" + assert pod_labels["test_global_label"] == "test_global_label_value" + assert pod_labels["test_component_label"] == "test_component_label_value" + assert pod_labels["common_label"] == "component_value" + + def test_should_separate_object_and_pod_labels(self, values_key, show_only, extra_values): + object_labels, pod_labels = _render( + values_key, + show_only, + extra_values, + {"labels": COMPONENT_LABELS, "podLabels": POD_LABELS}, + ) + + assert object_labels["test_global_label"] == "test_global_label_value" + assert object_labels["test_component_label"] == "test_component_label_value" + assert "test_pod_label" not in object_labels + assert object_labels["common_label"] == "component_value" + assert pod_labels["test_global_label"] == "test_global_label_value" + assert "test_component_label" not in pod_labels + assert pod_labels["test_pod_label"] == "test_pod_label_value" + assert pod_labels["common_label"] == "pod_value" + + def test_should_treat_empty_pod_labels_as_separate(self, values_key, show_only, extra_values): + object_labels, pod_labels = _render( + values_key, show_only, extra_values, {"labels": COMPONENT_LABELS, "podLabels": {}} + ) + + assert object_labels["test_component_label"] == "test_component_label_value" + assert "test_component_label" not in pod_labels + assert pod_labels["test_global_label"] == "test_global_label_value" + assert pod_labels["common_label"] == "global_value" + + +class TestOtelCollectorPodLabels: + """OTel Collector already merges component labels onto its Deployment.""" + + SHOW_ONLY = "templates/otel-collector/otel-collector-deployment.yaml" + + def test_should_keep_component_labels_on_both_when_pod_labels_are_unset(self): + object_labels, pod_labels = _render( + "otelCollector", self.SHOW_ONLY, OTEL_ENABLED, {"labels": COMPONENT_LABELS} + ) + + assert object_labels["test_component_label"] == "test_component_label_value" + assert pod_labels["test_component_label"] == "test_component_label_value" + assert object_labels["common_label"] == "component_value" + assert pod_labels["common_label"] == "component_value" + + def test_should_separate_object_and_pod_labels(self): + object_labels, pod_labels = _render( + "otelCollector", + self.SHOW_ONLY, + OTEL_ENABLED, + {"labels": COMPONENT_LABELS, "podLabels": POD_LABELS}, + ) + + assert object_labels["test_component_label"] == "test_component_label_value" + assert "test_pod_label" not in object_labels + assert "test_component_label" not in pod_labels + assert pod_labels["test_pod_label"] == "test_pod_label_value" + assert pod_labels["common_label"] == "pod_value" + + def test_should_treat_empty_pod_labels_as_separate(self): + object_labels, pod_labels = _render( + "otelCollector", + self.SHOW_ONLY, + OTEL_ENABLED, + {"labels": COMPONENT_LABELS, "podLabels": {}}, + ) + + assert object_labels["test_component_label"] == "test_component_label_value" + assert "test_component_label" not in pod_labels + assert pod_labels["test_global_label"] == "test_global_label_value" diff --git a/chart/tests/helm_tests/dagprocessor/test_labels_deployment.py b/chart/tests/helm_tests/dagprocessor/test_labels_deployment.py index ba5693eee66cb..8aef09acca69c 100644 --- a/chart/tests/helm_tests/dagprocessor/test_labels_deployment.py +++ b/chart/tests/helm_tests/dagprocessor/test_labels_deployment.py @@ -57,6 +57,82 @@ def test_should_add_component_specific_labels(self): == "test_component_label_value" ) + def test_should_preserve_component_labels_on_pods_when_pod_labels_are_unset(self): + docs = render_chart( + values={ + "labels": { + "test_global_label": "test_global_label_value", + "common_label": "global_value", + }, + "dagProcessor": { + "labels": { + "test_component_label": "test_component_label_value", + "common_label": "component_value", + }, + }, + }, + show_only=[self.TEMPLATE_FILE], + ) + + deployment_labels = jmespath.search("metadata.labels", docs[0]) + pod_labels = jmespath.search("spec.template.metadata.labels", docs[0]) + assert deployment_labels["test_global_label"] == "test_global_label_value" + assert "test_component_label" not in deployment_labels + assert deployment_labels["common_label"] == "global_value" + assert pod_labels["test_global_label"] == "test_global_label_value" + assert pod_labels["test_component_label"] == "test_component_label_value" + assert pod_labels["common_label"] == "component_value" + + def test_should_separate_deployment_and_pod_labels(self): + docs = render_chart( + values={ + "labels": { + "test_global_label": "test_global_label_value", + "common_label": "global_value", + }, + "dagProcessor": { + "labels": { + "test_component_label": "test_component_label_value", + "common_label": "component_value", + }, + "podLabels": { + "test_pod_label": "test_pod_label_value", + "common_label": "pod_value", + }, + }, + }, + show_only=[self.TEMPLATE_FILE], + ) + + deployment_labels = jmespath.search("metadata.labels", docs[0]) + pod_labels = jmespath.search("spec.template.metadata.labels", docs[0]) + assert deployment_labels["test_global_label"] == "test_global_label_value" + assert deployment_labels["test_component_label"] == "test_component_label_value" + assert "test_pod_label" not in deployment_labels + assert deployment_labels["common_label"] == "component_value" + assert pod_labels["test_global_label"] == "test_global_label_value" + assert "test_component_label" not in pod_labels + assert pod_labels["test_pod_label"] == "test_pod_label_value" + assert pod_labels["common_label"] == "pod_value" + + def test_should_treat_empty_pod_labels_as_separate(self): + docs = render_chart( + values={ + "labels": {"test_global_label": "test_global_label_value"}, + "dagProcessor": { + "labels": {"test_component_label": "test_component_label_value"}, + "podLabels": {}, + }, + }, + show_only=[self.TEMPLATE_FILE], + ) + + deployment_labels = jmespath.search("metadata.labels", docs[0]) + pod_labels = jmespath.search("spec.template.metadata.labels", docs[0]) + assert deployment_labels["test_component_label"] == "test_component_label_value" + assert "test_component_label" not in pod_labels + assert pod_labels["test_global_label"] == "test_global_label_value" + def test_should_merge_global_and_component_specific_labels(self): """Test adding both .Values.labels and .Values.dagProcessor.labels.""" docs = render_chart( diff --git a/chart/values.schema.json b/chart/values.schema.json index a23ee0c0a9b97..d1f46faac3366 100644 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -2397,13 +2397,24 @@ } }, "labels": { - "description": "Labels to add to the Airflow Celery workers objects.", + "description": "Labels for Airflow Celery workers objects. If podLabels is not set, these labels are applied to pods but not the Deployment for backward compatibility.", "type": "object", "default": {}, "additionalProperties": { "type": "string" } }, + "podLabels": { + "description": "Labels for Airflow Celery workers pods. Set to an empty object to use only global labels on pods.", + "type": [ + "object", + "null" + ], + "default": null, + "additionalProperties": { + "type": "string" + } + }, "logGroomerSidecar": { "description": "Configuration for Airflow Celery worker log groomer sidecar.", "type": "object", @@ -3598,13 +3609,24 @@ } }, "labels": { - "description": "Labels to add to the scheduler objects and pods.", + "description": "Labels for scheduler objects. If podLabels is not set, these labels are applied to pods but not the Deployment for backward compatibility.", "type": "object", "default": {}, "additionalProperties": { "type": "string" } }, + "podLabels": { + "description": "Labels for scheduler pods. Set to an empty object to use only global labels on pods.", + "type": [ + "object", + "null" + ], + "default": null, + "additionalProperties": { + "type": "string" + } + }, "logGroomerSidecar": { "$ref": "#/definitions/logGroomerConfigType", "description": "Configuration for the schedulers log groomer sidecar." @@ -4154,13 +4176,24 @@ } }, "labels": { - "description": "Labels to add to the triggerer objects and pods.", + "description": "Labels for triggerer objects. If podLabels is not set, these labels are applied to pods but not the Deployment for backward compatibility.", "type": "object", "default": {}, "additionalProperties": { "type": "string" } }, + "podLabels": { + "description": "Labels for triggerer pods. Set to an empty object to use only global labels on pods.", + "type": [ + "object", + "null" + ], + "default": null, + "additionalProperties": { + "type": "string" + } + }, "containerLifecycleHooks": { "description": "Container Lifecycle Hooks definition for the triggerer. If not set, the values from global `containerLifecycleHooks` will be used.", "type": "object", @@ -4883,13 +4916,24 @@ } }, "labels": { - "description": "Labels specific to dag processor objects and pods", + "description": "Labels for dag processor objects. If podLabels is not set, these labels are applied to pods but not the Deployment for backward compatibility.", "type": "object", "default": {}, "additionalProperties": { "type": "string" } }, + "podLabels": { + "description": "Labels for dag processor pods. Set to an empty object to use only global labels on pods.", + "type": [ + "object", + "null" + ], + "default": null, + "additionalProperties": { + "type": "string" + } + }, "env": { "description": "Add additional env vars to dag processor.", "type": "array", @@ -6370,13 +6414,24 @@ } }, "labels": { - "description": "Labels to add to the API server objects and pods.", + "description": "Labels for Airflow API server objects. If podLabels is not set, these labels are applied to pods but not the Deployment for backward compatibility.", "type": "object", "default": {}, "additionalProperties": { "type": "string" } }, + "podLabels": { + "description": "Labels for Airflow API server pods. Set to an empty object to use only global labels on pods.", + "type": [ + "object", + "null" + ], + "default": null, + "additionalProperties": { + "type": "string" + } + }, "waitForMigrations": { "description": "wait-for-airflow-migrations init container.", "type": "object", @@ -7015,13 +7070,24 @@ } }, "labels": { - "description": "Labels to add to the flower objects and pods.", + "description": "Labels for flower objects. If podLabels is not set, these labels are applied to pods but not the Deployment for backward compatibility.", "type": "object", "default": {}, "additionalProperties": { "type": "string" } }, + "podLabels": { + "description": "Labels for flower pods. Set to an empty object to use only global labels on pods.", + "type": [ + "object", + "null" + ], + "default": null, + "additionalProperties": { + "type": "string" + } + }, "containerLifecycleHooks": { "description": "Container Lifecycle Hooks definition for the network policy. If not set, the values from global `containerLifecycleHooks` will be used.", "type": "object", @@ -7429,13 +7495,24 @@ ] }, "labels": { - "description": "Labels specific to statsd objects and pods", + "description": "Labels for statsd objects. If podLabels is not set, these labels are applied to pods but not the Deployment for backward compatibility.", "type": "object", "default": {}, "additionalProperties": { "type": "string" } }, + "podLabels": { + "description": "Labels for statsd pods. Set to an empty object to use only global labels on pods.", + "type": [ + "object", + "null" + ], + "default": null, + "additionalProperties": { + "type": "string" + } + }, "env": { "description": "Add additional env vars to statsd container.", "type": "array", @@ -7637,13 +7714,24 @@ "default": null }, "labels": { - "description": "Labels specific to OTel Collector objects and pods.", + "description": "Labels to add to the OTel Collector objects, and to its pods when podLabels is not set.", "type": "object", "default": {}, "additionalProperties": { "type": "string" } }, + "podLabels": { + "description": "Labels for OTel Collector pods. Defaults to labels when unset. Set to an empty object to use only global labels on pods.", + "type": [ + "object", + "null" + ], + "default": null, + "additionalProperties": { + "type": "string" + } + }, "podAnnotations": { "description": "Annotations to add to the OTel Collector pods (templated).", "type": "object", @@ -7744,13 +7832,24 @@ } }, "labels": { - "description": "Labels to add to the PgBouncer objects and pods.", + "description": "Labels for PgBouncer objects. If podLabels is not set, these labels are applied to pods but not the Deployment for backward compatibility.", "type": "object", "default": {}, "additionalProperties": { "type": "string" } }, + "podLabels": { + "description": "Labels for PgBouncer pods. Set to an empty object to use only global labels on pods.", + "type": [ + "object", + "null" + ], + "default": null, + "additionalProperties": { + "type": "string" + } + }, "enabled": { "description": "Enable PgBouncer.", "type": "boolean", @@ -8674,13 +8773,24 @@ } }, "labels": { - "description": "Labels to add to the redis objects and pods.", + "description": "Labels for redis objects. If podLabels is not set, these labels are applied to pods but not the StatefulSet for backward compatibility.", "type": "object", "default": {}, "additionalProperties": { "type": "string" } }, + "podLabels": { + "description": "Labels for redis pods. Set to an empty object to use only global labels on pods.", + "type": [ + "object", + "null" + ], + "default": null, + "additionalProperties": { + "type": "string" + } + }, "uid": { "description": "Redis run as user parameter.", "type": "integer", diff --git a/chart/values.yaml b/chart/values.yaml index a1c057239d86a..c65f9f0fc3481 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -931,9 +931,13 @@ workers: # Pod annotations for the Airflow Celery workers (templated) podAnnotations: {} - # Labels specific to Airflow Celery workers objects + # Labels for Airflow Celery workers objects. If podLabels is not set, these labels are + # applied to pods but not the Deployment for backward compatibility. labels: {} + # Labels for Airflow Celery workers pods. Set to {} to use only global labels on pods. + podLabels: ~ + # Log groomer configuration for Airflow Celery workers logGroomerSidecar: # Whether to deploy the Airflow Celery worker log groomer sidecar @@ -1332,9 +1336,13 @@ scheduler: # Pod annotations for scheduler pods (templated) podAnnotations: {} - # Labels specific to scheduler objects and pods + # Labels for scheduler objects. If podLabels is not set, these labels are + # applied to pods but not the Deployment for backward compatibility. labels: {} + # Labels for scheduler pods. Set to {} to use only global labels on pods. + podLabels: ~ + logGroomerSidecar: # Whether to deploy the Airflow scheduler log groomer sidecar. enabled: true @@ -1661,9 +1669,13 @@ apiServer: # Max number of old ReplicaSets to retain revisionHistoryLimit: ~ - # Labels specific to Airflow API server objects and pods + # Labels for Airflow API server objects. If podLabels is not set, these labels are + # applied to pods but not the Deployment for backward compatibility. labels: {} + # Labels for Airflow API server pods. Set to {} to use only global labels on pods. + podLabels: ~ + # Command to use when running the Airflow API server (templated). command: ~ @@ -2017,9 +2029,13 @@ triggerer: # Pod annotations for triggerer pods (templated) podAnnotations: {} - # Labels specific to triggerer objects and pods + # Labels for triggerer objects. If podLabels is not set, these labels are + # applied to pods but not the Deployment for backward compatibility. labels: {} + # Labels for triggerer pods. Set to {} to use only global labels on pods. + podLabels: ~ + logGroomerSidecar: # Whether to deploy the Airflow triggerer log groomer sidecar. enabled: true @@ -2316,9 +2332,13 @@ dagProcessor: securityContexts: container: {} - # Labels specific to dag processor objects + # Labels for dag processor objects. If podLabels is not set, these labels are + # applied to pods but not the Deployment for backward compatibility. labels: {} + # Labels for dag processor pods. Set to {} to use only global labels on pods. + podLabels: ~ + # Environment variables to add to dag processor container env: [] @@ -2524,9 +2544,13 @@ flower: # Pod annotations for flower pods (templated) podAnnotations: {} - # Labels specific to flower objects and pods + # Labels for flower objects. If podLabels is not set, these labels are + # applied to pods but not the Deployment for backward compatibility. labels: {} + # Labels for flower pods. Set to {} to use only global labels on pods. + podLabels: ~ + env: [] # StatsD settings @@ -2637,9 +2661,13 @@ statsd: # Pod annotations for StatsD pods (templated) podAnnotations: {} - # Labels specific to StatsD objects and pods + # Labels for StatsD objects. If podLabels is not set, these labels are + # applied to pods but not the Deployment for backward compatibility. labels: {} + # Labels for StatsD pods. Set to {} to use only global labels on pods. + podLabels: ~ + # Environment variables to add to StatsD container env: [] @@ -2721,6 +2749,10 @@ otelCollector: topologySpreadConstraints: [] priorityClassName: ~ labels: {} + + # Labels for OTel Collector pods. Defaults to labels when unset. Set to {} to + # use only global labels on pods. + podLabels: ~ podAnnotations: {} securityContexts: @@ -2961,9 +2993,13 @@ pgbouncer: # mountPath: "{{ .Values.my_custom_path }}" # readOnly: true - # Labels specific to PgBouncer objects and pods + # Labels for PgBouncer objects. If podLabels is not set, these labels are + # applied to pods but not the Deployment for backward compatibility. labels: {} + # Labels for PgBouncer pods. Set to {} to use only global labels on pods. + podLabels: ~ + # Environment variables to add to PgBouncer container env: [] @@ -3067,9 +3103,13 @@ redis: # Container level lifecycle hooks containerLifecycleHooks: {} - # Labels specific to redis objects and pods + # Labels for redis objects. If podLabels is not set, these labels are + # applied to pods but not the StatefulSet for backward compatibility. labels: {} + # Labels for redis pods. Set to {} to use only global labels on pods. + podLabels: ~ + # Pod annotations for Redis pods (templated) podAnnotations: {}