diff --git a/charts/radar-hub/Chart.yaml b/charts/radar-hub/Chart.yaml index e27c5a6..f2165a8 100644 --- a/charts/radar-hub/Chart.yaml +++ b/charts/radar-hub/Chart.yaml @@ -3,7 +3,7 @@ name: radar-hub description: Radar Cloud control plane for self-hosted deployments — Go API + React web app + Postgres (bundled eval DB or bring-your-own). type: application -version: 1.14.1 +version: 1.15.0-rc.1 # The Hub release this chart installs. image.hub.tag and image.web.tag both # default to it when unset. Deliberately independent of `version` above — see # README.md. Rewritten by the release job in skyhook-dev/radar-hub; do not carry diff --git a/charts/radar-hub/README.md b/charts/radar-hub/README.md index 98f3166..3eaad9a 100644 --- a/charts/radar-hub/README.md +++ b/charts/radar-hub/README.md @@ -161,6 +161,31 @@ See [`values.yaml`](values.yaml) for the full set, and [`values.schema.json`](values.schema.json) for the enforced schema. Invalid values are rejected at install time rather than surfacing as a broken pod. +## Pod labels + +`podLabels` adds labels to every pod the chart runs: hub, web and the bundled +Postgres. On Hub releases that support it, they also go on the AI +investigation Jobs the hub starts. `hub.installPodLabels` adds labels to the +Radar install commands the hub shows, so the agent in each connected cluster +carries them; older Hub releases ignore it. + +If you bought Radar through Google Cloud Marketplace, set Google's +consumption-tracking label in both: + +```yaml +podLabels: + goog-partner-solution: +hub: + installPodLabels: + goog-partner-solution: +``` + +Keys and values follow Kubernetes label syntax, which the schema enforces. The +chart's own selector labels (`app.kubernetes.io/name`, +`app.kubernetes.io/instance` and `app.kubernetes.io/component`) can't be set +through `podLabels`, nor the Radar agent chart's (`app.kubernetes.io/name` and +`app.kubernetes.io/instance`) through `hub.installPodLabels`. + ## AI agent Release prerequisite: this feature requires a Hub release with Anthropic provider diff --git a/charts/radar-hub/templates/_helpers.tpl b/charts/radar-hub/templates/_helpers.tpl index 864f33d..322c7bf 100644 --- a/charts/radar-hub/templates/_helpers.tpl +++ b/charts/radar-hub/templates/_helpers.tpl @@ -39,6 +39,53 @@ app.kubernetes.io/name: {{ include "radar-hub.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} +{{/* +.Values.podLabels as YAML for a pod template's labels, or nothing. The chart's +own selector keys are refused: repeated in the same map they render a +duplicate key, and if a duplicate won it would detach the pod from its +Deployment or StatefulSet. +*/}} +{{- define "radar-hub.podLabels" -}} +{{- $reserved := list "app.kubernetes.io/name" "app.kubernetes.io/instance" "app.kubernetes.io/component" -}} +{{- range $k, $_ := .Values.podLabels -}} +{{- if has $k $reserved -}} +{{- fail (printf "podLabels: %s is one of the chart's own selector labels and can't be set" $k) -}} +{{- end -}} +{{- end -}} +{{- with .Values.podLabels -}} +{{- toYaml . -}} +{{- end -}} +{{- end }} + +{{/* +.Values.hub.installPodLabels as key=value,key=value for HUB_INSTALL_POD_LABELS, +or nothing. The Radar agent chart's selector keys are refused: the hub puts +these labels on every agent's pod template, where either key would render a +duplicate or detach the pod from its Deployment. +*/}} +{{- define "radar-hub.installPodLabels" -}} +{{- $reserved := list "app.kubernetes.io/name" "app.kubernetes.io/instance" -}} +{{- range $k, $_ := .Values.hub.installPodLabels -}} +{{- if has $k $reserved -}} +{{- fail (printf "hub.installPodLabels: %s is one of the Radar chart's selector labels and can't be set" $k) -}} +{{- end -}} +{{- end -}} +{{- include "radar-hub.labelList" .Values.hub.installPodLabels -}} +{{- end }} + +{{/* +A label map as key=value,key=value for an environment variable. Label syntax +(enforced by the schema) has no "," or "=" in keys or values, so the hub can +split it back unambiguously. Keys come out sorted. +*/}} +{{- define "radar-hub.labelList" -}} +{{- $pairs := list -}} +{{- range $k, $v := . -}} +{{- $pairs = append $pairs (printf "%s=%s" $k $v) -}} +{{- end -}} +{{- join "," $pairs -}} +{{- end }} + {{- define "radar-hub.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} {{- default (include "radar-hub.fullname" .) .Values.serviceAccount.name }} diff --git a/charts/radar-hub/templates/deployment-hub.yaml b/charts/radar-hub/templates/deployment-hub.yaml index 33e2e8a..6137144 100644 --- a/charts/radar-hub/templates/deployment-hub.yaml +++ b/charts/radar-hub/templates/deployment-hub.yaml @@ -23,6 +23,9 @@ spec: labels: {{- include "radar-hub.selectorLabels" . | nindent 8 }} app.kubernetes.io/component: hub + {{- with include "radar-hub.podLabels" . }} + {{- . | nindent 8 }} + {{- end }} spec: serviceAccountName: {{ include "radar-hub.serviceAccountName" . }} {{- with .Values.imagePullSecrets }} @@ -389,6 +392,13 @@ spec: - name: HUB_ENROLLMENT_APPROVAL value: "manual" {{- end }} + {{- /* Labels for the Radar install commands the hub shows. + Emitted only when set, so an install that sets nothing + renders the same env as before. */}} + {{- with include "radar-hub.installPodLabels" . }} + - name: HUB_INSTALL_POD_LABELS + value: {{ . | quote }} + {{- end }} {{- if include "radar-hub.webTLSIsPublic" . }} {{- /* The certificate that serves the hub's public address, public half only, so the hub can hand it to agents that @@ -452,6 +462,12 @@ spec: - name: HUB_AGENT_ENGINE_LABEL value: {{ . | quote }} {{- end }} + {{- /* podLabels go on the investigation Jobs too, so every pod + the release causes carries them. */}} + {{- with include "radar-hub.labelList" .Values.podLabels }} + - name: HUB_AGENT_JOB_POD_LABELS + value: {{ . | quote }} + {{- end }} {{- /* Fleet-wide kill switch for the AI analysis alert rules can attach to the issues they match. Emitted only when on: the hub defaults it off, so an absent variable is the same diff --git a/charts/radar-hub/templates/deployment-web.yaml b/charts/radar-hub/templates/deployment-web.yaml index 6ca5fa5..1eb4cfb 100644 --- a/charts/radar-hub/templates/deployment-web.yaml +++ b/charts/radar-hub/templates/deployment-web.yaml @@ -31,6 +31,9 @@ spec: labels: {{- include "radar-hub.selectorLabels" . | nindent 8 }} app.kubernetes.io/component: web + {{- with include "radar-hub.podLabels" . }} + {{- . | nindent 8 }} + {{- end }} spec: serviceAccountName: {{ include "radar-hub.serviceAccountName" . }} {{- with .Values.imagePullSecrets }} diff --git a/charts/radar-hub/templates/postgres-bundled.yaml b/charts/radar-hub/templates/postgres-bundled.yaml index 7ecdcab..cbcf25f 100644 --- a/charts/radar-hub/templates/postgres-bundled.yaml +++ b/charts/radar-hub/templates/postgres-bundled.yaml @@ -89,6 +89,9 @@ spec: labels: {{- include "radar-hub.selectorLabels" . | nindent 8 }} app.kubernetes.io/component: postgres + {{- with include "radar-hub.podLabels" . }} + {{- . | nindent 8 }} + {{- end }} spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: diff --git a/charts/radar-hub/tests/render-matrix.sh b/charts/radar-hub/tests/render-matrix.sh index dd46aa4..d999613 100755 --- a/charts/radar-hub/tests/render-matrix.sh +++ b/charts/radar-hub/tests/render-matrix.sh @@ -442,6 +442,31 @@ if notes "${ING[@]}" | grep -qF "In-cluster agent address"; then printf ' FAIL else printf ' ok %-46s absent\n' "no in-cluster address without a listener"; fi rm -rf "$scratch" +echo "pod labels" +PL=(--set-string podLabels.goog-partner-solution=isol_plb32_0123abc) +# 8 spaces: under spec.template.metadata.labels, not the Deployment's own labels. +PLRE='^ {8}goog-partner-solution: isol_plb32_0123abc$' +has "hub pod carries podLabels" "$PLRE" "${PL[@]}" --show-only templates/deployment-hub.yaml +has "web pod carries podLabels" "$PLRE" "${PL[@]}" --show-only templates/deployment-web.yaml +has "bundled Postgres pod carries podLabels" "$PLRE" "${PL[@]}" --show-only templates/postgres-bundled.yaml +lacks "no podLabels by default" 'goog-partner-solution' +check "a selector key in podLabels" refuse --set-string 'podLabels.app\.kubernetes\.io/component=x' +check "a non-string podLabels value" refuse --set podLabels.team=1 +check "a podLabels value with a space" refuse --set-string 'podLabels.team=a b' +check "a podLabels key with a space" refuse --set-string 'podLabels.bad key=x' +check "a 64-character install label value" refuse --set-string "hub.installPodLabels.x=$(printf 'a%.0s' $(seq 64))" +check "a prefixed label key" render --set-string 'podLabels.example\.com/team=platform' +check "a 253-character label prefix" render --set-string "podLabels.$(printf 'a%.0s' $(seq 253))/team=x" +check "a 254-character label prefix" refuse --set-string "podLabels.$(printf 'a%.0s' $(seq 254))/team=x" +check "an agent selector key in installPodLabels" refuse --set-string 'hub.installPodLabels.app\.kubernetes\.io/name=x' +check "the other agent selector key" refuse --set-string 'hub.installPodLabels.app\.kubernetes\.io/instance=x' +envis "installPodLabels reach the hub" HUB_INSTALL_POD_LABELS 'a=1,goog-partner-solution=isol_x' --set-string hub.installPodLabels.goog-partner-solution=isol_x --set-string hub.installPodLabels.a=1 +lacks "no install-labels env by default" 'name: HUB_INSTALL_POD_LABELS' +AGF=(-f ci/agent-anthropic-values.yaml) +envis "podLabels reach the AI investigation Jobs" HUB_AGENT_JOB_POD_LABELS 'goog-partner-solution=isol_plb32_0123abc' "${AGF[@]}" "${PL[@]}" +lacks "no Job-labels env without podLabels" 'name: HUB_AGENT_JOB_POD_LABELS' "${AGF[@]}" +lacks "no Job-labels env with the agent off" 'name: HUB_AGENT_JOB_POD_LABELS' "${PL[@]}" + echo if [ $fails -eq 0 ]; then echo "all checks passed"; else echo "$fails check(s) failed"; fi exit $fails diff --git a/charts/radar-hub/values.schema.json b/charts/radar-hub/values.schema.json index 7e2cc6b..c4b3537 100644 --- a/charts/radar-hub/values.schema.json +++ b/charts/radar-hub/values.schema.json @@ -201,6 +201,9 @@ "podAnnotations": { "type": "object" }, + "installPodLabels": { + "$ref": "#/definitions/labels" + }, "nodeSelector": { "type": "object" }, @@ -808,6 +811,9 @@ "securityContext": { "type": "object" }, + "podLabels": { + "$ref": "#/definitions/labels" + }, "nameOverride": { "type": "string" }, @@ -815,5 +821,21 @@ "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "definitions": { + "labels": { + "description": "Kubernetes labels: keys are an optional DNS-subdomain prefix (at most 253 characters) and a name of at most 63 characters; values are strings of at most 63 letters, digits, '-', '_' or '.', starting and ending with a letter or digit.", + "type": "object", + "propertyNames": { + "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?[A-Za-z0-9]([-A-Za-z0-9_.]{0,61}[A-Za-z0-9])?$", + "not": { + "pattern": "^[^/]{254,}/" + } + }, + "additionalProperties": { + "type": "string", + "pattern": "^(([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9])?$" + } + } + } } diff --git a/charts/radar-hub/values.yaml b/charts/radar-hub/values.yaml index 75cc0b5..7539ce7 100644 --- a/charts/radar-hub/values.yaml +++ b/charts/radar-hub/values.yaml @@ -335,6 +335,11 @@ hub: memory: 1Gi podAnnotations: {} + # Labels the hub adds to the Radar install commands it shows for connecting + # a cluster, so the Radar agent in each cluster carries them. Bought through + # Google Cloud Marketplace? Set the same label as podLabels. Needs a Hub + # release that supports it; older ones ignore it. + installPodLabels: {} # Official images are published for linux/amd64 and linux/arm64, so only the # OS is pinned by default. Add kubernetes.io/arch: to constrain # it; Helm merges that key with this default selector. @@ -819,6 +824,17 @@ securityContext: drop: ["ALL"] readOnlyRootFilesystem: false # web image needs writable /var/cache/nginx + /tmp/run +# Extra labels on every pod this chart runs (hub, web and the bundled +# Postgres) and, on Hub releases that support it, on the AI investigation Jobs +# the hub starts. Bought through Google Cloud Marketplace? Set Google's +# consumption-tracking label here, and in hub.installPodLabels: +# podLabels: +# goog-partner-solution: +# Kubernetes label syntax. The chart's own selector labels +# (app.kubernetes.io/name, app.kubernetes.io/instance and +# app.kubernetes.io/component) can't be set here. +podLabels: {} + # --------------------------------------------------------------------------- # Names # ---------------------------------------------------------------------------