Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/55.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adds a chart-wide `podLabels` value, applied to every pod the chart renders.
4 changes: 4 additions & 0 deletions helm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ For ephemeral test deployments (no persistence across upgrades), `/ref` can also
| `imagePullSecrets` | Docker registry secrets | `[]` |
| `nameOverride` | Override chart name | `""` |
| `fullnameOverride` | Override full release name | `""` |
| `podLabels` | Labels added to every pod | `{}` |

`podLabels` covers every pod the chart renders.
A component's own `podLabels` is applied on top of it so they take precendence.

### Diagnostic providers

Expand Down
10 changes: 10 additions & 0 deletions helm/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Labels added to every pod the chart renders, from the chart-wide `podLabels`.
Rendered before a component's own podLabels, so a component can still override one.
*/}}
{{- define "ref.podLabels" -}}
{{- with .Values.podLabels -}}
{{- toYaml . -}}
{{- end -}}
{{- end -}}

{{/*
Selector labels
*/}}
Expand Down
3 changes: 3 additions & 0 deletions helm/templates/api/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ spec:
labels:
app.kubernetes.io/component: api
{{- include "ref.labels" . | nindent 8 }}
{{- with (include "ref.podLabels" .) }}
{{- . | nindent 8 }}
{{- end }}
Comment on lines +32 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Protect selector-owned labels from configurable overrides.

Global and component podLabels are rendered after canonical selector labels. If either map contains a selector identity key such as app.kubernetes.io/name, app.kubernetes.io/instance, or the component label, the pod template can contain duplicate or mismatched values while spec.selector remains unchanged. Kubernetes may reject the Deployment or leave pods unmatched, and the same configuration can affect multiple workloads. Reject selector-reserved keys or render canonical selector labels last, with regression coverage for each Deployment.

📍 Affects 2 files
  • helm/templates/api/deployment.yaml#L32-L34 (this comment)
  • helm/templates/flower/deployment.yaml#L27-L29

Source: MCP tools

{{- with .Values.api.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
Expand Down
3 changes: 3 additions & 0 deletions helm/templates/flower/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ spec:
labels:
app.kubernetes.io/component: flower
{{- include "ref.labels" . | nindent 8 }}
{{- with (include "ref.podLabels" .) }}
{{- . | nindent 8 }}
{{- end }}
{{- with .Values.flower.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
Expand Down
3 changes: 3 additions & 0 deletions helm/templates/migrate-job.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ spec:
metadata:
labels:
{{- include "ref.selectorLabels" . | nindent 8 }}
{{- with (include "ref.podLabels" .) }}
{{- . | nindent 8 }}
{{- end }}
spec:
{{- with $migrate.priorityClassName }}
priorityClassName: {{ . | quote }}
Expand Down
3 changes: 3 additions & 0 deletions helm/templates/providers/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ spec:
labels:
app.kubernetes.io/component: {{ $instance }}
{{- include "ref.labels" $ | nindent 8 }}
{{- with (include "ref.podLabels" $) }}
{{- . | nindent 8 }}
{{- end }}
{{- with $spec.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
Expand Down
3 changes: 3 additions & 0 deletions helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

# Labels added to every pod the chart renders, on top of each component's own podLabels.
podLabels: {}

api:
enabled: true
replicaCount: 1
Expand Down
45 changes: 45 additions & 0 deletions tests/test_helm_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -1064,3 +1064,48 @@ def test_the_migrate_job_follows_an_orchestrator_priority_class_override():
"orchestrator.priorityClassName=ref-orchestrator",
)
assert _pod_spec(docs, "migrate", kind="Job")["priorityClassName"] == "ref-orchestrator"


def _pod_labels(docs: list[dict], component: str, kind: str = "Deployment") -> dict:
return find(docs, kind, f"-{component}")["spec"]["template"]["metadata"]["labels"]


POD_TEMPLATES = [
("api", "Deployment"),
("flower", "Deployment"),
("orchestrator", "Deployment"),
("pmp", "Deployment"),
("migrate", "Job"),
]


@pytest.mark.parametrize("component,kind", POD_TEMPLATES)
def test_chart_wide_pod_labels_reach_every_pod(component, kind):
docs = render("flower.enabled=true", "podLabels.environment=production")
assert _pod_labels(docs, component, kind=kind)["environment"] == "production"


@pytest.mark.parametrize("component,kind", POD_TEMPLATES)
def test_no_chart_wide_pod_labels_by_default(component, kind):
docs = render("flower.enabled=true")
assert "environment" not in _pod_labels(docs, component, kind=kind)


def test_chart_wide_pod_labels_stay_off_the_selectors():
# A Deployment's selector is immutable
# A label that reached it would make the next upgrade of an existing release fail.
docs = render("podLabels.environment=production")
for component in ("api", "orchestrator", "pmp"):
selector = find(docs, "Deployment", f"-{component}")["spec"]["selector"]["matchLabels"]
assert "environment" not in selector


def test_a_component_overrides_a_chart_wide_pod_label():
docs = render(
"podLabels.environment=production",
"api.podLabels.environment=staging",
"providers.pmp.podLabels.environment=staging",
)
assert _pod_labels(docs, "api")["environment"] == "staging"
assert _pod_labels(docs, "pmp")["environment"] == "staging"
assert _pod_labels(docs, "orchestrator")["environment"] == "production"
Loading