Files
helm-charts/patchmon/templates/patchmon.deployment.yaml
jklein 03c9fd6467 Add null-safe hardening, probes and resources to patchmon
Third change of the same class (#99): an override written without
children is YAML null and replaced the whole default sub-tree, rendering
"securityContext: null" (unhardened container), a vanished probe or a
container without resource accounting - silently, while the workload
reports healthy.

The defaultedDict helper from #96 is adopted for both workloads, so an
empty block means "chart defaults apply" while an explicitly set value
still wins, including false and 0.

The server probes rebuild their Host header from ingress.domain via the
passed-in root context, because the 2.x server rejects requests whose
Host is neither loopback nor a CORS_ORIGIN host.

The schema types allow null for exactly these blocks, so schema and
helper agree instead of the schema rejecting what the helper absorbs.

Rendering with normal values is byte-identical to the previous version,
with and without the guacd sidecar.
2026-08-23 20:19:08 +02:00

234 lines
10 KiB
YAML

apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
labels:
app: {{ .Release.Name }}-{{ .Chart.Name }}
spec:
replicas: {{ include "patchmon.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.patchmon.server.replicas) }}
selector:
matchLabels:
app: {{ .Release.Name }}-{{ .Chart.Name }}
template:
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}
labels:
app: {{ .Release.Name }}-{{ .Chart.Name }}
spec:
automountServiceAccountToken: false
# Pin a stable pod hostname: the server identifies itself in the agent
# presence registry (Redis) by hostname, and a Deployment pod's default
# hostname changes on every rollout, which would leave stale presence
# records behind (upstream pins "patchmon-server" in its compose file
# for the same reason).
hostname: {{ .Release.Name }}-{{ .Chart.Name }}-server
securityContext:
{{- include "patchmon.server.podSecurityContext" .Values.patchmon.server.securityContext | nindent 8 }}
containers:
- name: {{ .Release.Name }}-{{ .Chart.Name }}-server
image: "ghcr.io/patchmon/patchmon-server:{{ .Chart.AppVersion }}"
imagePullPolicy: IfNotPresent
securityContext:
{{- include "patchmon.server.containerSecurityContext" .Values.patchmon.server.securityContext | nindent 12 }}
ports:
- containerPort: 3000
protocol: TCP
name: http
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-postgres-secret
key: database-url
- name: REDIS_HOST
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-redis-secret
key: host
- name: REDIS_PORT
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-redis-secret
key: port
- name: REDIS_USER
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-redis-secret
key: username
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-redis-secret
key: password
- name: REDIS_DB
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-redis-secret
key: db
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-patchmon-secret
key: jwt-secret
- name: AI_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-patchmon-secret
key: encryption-key
- name: SESSION_SECRET
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-patchmon-secret
key: session-secret
- name: OIDC_ENABLED
value: "true"
- name: OIDC_ISSUER_URL
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-oidc-secret
key: issuer-url
- name: OIDC_CLIENT_ID
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-oidc-secret
key: client-id
- name: OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-oidc-secret
key: client-secret
- name: OIDC_REDIRECT_URI
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-oidc-secret
key: redirect-uri
- name: OIDC_SCOPES
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-oidc-secret
key: requested-scopes
- name: OIDC_AUTO_CREATE_USERS
value: "true"
# 2.x role vocabulary: superadmin, admin, host_manager, readonly,
# user. "viewer" (the 1.x default of this chart) no longer exists;
# readonly is its least-privilege equivalent.
- name: OIDC_DEFAULT_ROLE
value: "readonly"
- name: OIDC_DISABLE_LOCAL_AUTH
value: "true"
- name: OIDC_BUTTON_TEXT
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-oidc-secret
key: button-text
- name: OIDC_SYNC_ROLES
value: "true"
- name: OIDC_USER_GROUP
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-oidc-secret
key: user-group-name
- name: OIDC_ADMIN_GROUP
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-oidc-secret
key: admin-group-name
- name: CORS_ORIGIN
value: "https://{{ required "A Domain/Host must be provided" .Values.ingress.domain }}"
# Renamed upstream in 2.x (was TRUST_PROXIES=<count> in 1.x): the
# server sits behind the ingress-nginx reverse proxy.
- name: TRUST_PROXY
value: "true"
{{- if .Values.patchmon.guacd.enabled }}
- name: GUACD_ADDRESS
value: "127.0.0.1:4822"
{{- end }}
{{- range $value := .Values.patchmon.server.env }}
{{- if $value.value }}
- name: {{ $value.name | quote }}
value: {{ tpl $value.value $ | quote }}
{{- else if $value.valueFrom }}
{{- if $value.valueFrom.secretKeyRef }}
- name: {{ $value.name }}
valueFrom:
secretKeyRef:
name: {{ tpl $value.valueFrom.secretKeyRef.name $ | quote }}
key: {{ tpl $value.valueFrom.secretKeyRef.key $ | quote }}
{{- else if $value.valueFrom.configMapKeyRef }}
- name: {{ $value.name }}
valueFrom:
configMapKeyRef:
name: {{ tpl $value.valueFrom.configMapKeyRef.name $ | quote }}
key: {{ tpl $value.valueFrom.configMapKeyRef.key $ | quote }}
{{- end }}
{{- end }}
{{- end }}
# Probe values are rendered through tpl (see values.yaml for the
# defaults and why the probes send a Host header).
livenessProbe:
{{- tpl (include "patchmon.server.livenessProbe" (dict "given" .Values.patchmon.server.livenessProbe "root" $)) . | nindent 12 }}
readinessProbe:
{{- tpl (include "patchmon.server.readinessProbe" (dict "given" .Values.patchmon.server.readinessProbe "root" $)) . | nindent 12 }}
startupProbe:
{{- tpl (include "patchmon.server.startupProbe" (dict "given" .Values.patchmon.server.startupProbe "root" $)) . | nindent 12 }}
resources:
{{- include "patchmon.server.effectiveResources" .Values.patchmon.server.resources | nindent 12 }}
volumeMounts:
# The image is stateless, but the Go server needs a writable /tmp
# for scratch files with a read-only root filesystem.
- name: tmp
mountPath: /tmp
{{- if .Values.patchmon.guacd.enabled }}
- name: {{ .Release.Name }}-{{ .Chart.Name }}-guacd
image: {{ .Values.patchmon.guacd.image }}
imagePullPolicy: IfNotPresent
{{- if .Values.patchmon.guacd.env }}
env:
{{- range $value := .Values.patchmon.guacd.env }}
{{- if $value.value }}
- name: {{ $value.name | quote }}
value: {{ tpl $value.value $ | quote }}
{{- else if $value.valueFrom }}
{{- if $value.valueFrom.secretKeyRef }}
- name: {{ $value.name }}
valueFrom:
secretKeyRef:
name: {{ tpl $value.valueFrom.secretKeyRef.name $ | quote }}
key: {{ tpl $value.valueFrom.secretKeyRef.key $ | quote }}
{{- else if $value.valueFrom.configMapKeyRef }}
- name: {{ $value.name }}
valueFrom:
configMapKeyRef:
name: {{ tpl $value.valueFrom.configMapKeyRef.name $ | quote }}
key: {{ tpl $value.valueFrom.configMapKeyRef.key $ | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
securityContext:
{{- include "patchmon.guacd.containerSecurityContext" .Values.patchmon.guacd.securityContext | nindent 12 }}
ports:
- containerPort: 4822
protocol: TCP
name: guacd
livenessProbe:
{{- tpl (include "patchmon.guacd.livenessProbe" .Values.patchmon.guacd.livenessProbe) . | nindent 12 }}
readinessProbe:
{{- tpl (include "patchmon.guacd.readinessProbe" .Values.patchmon.guacd.readinessProbe) . | nindent 12 }}
startupProbe:
{{- tpl (include "patchmon.guacd.startupProbe" .Values.patchmon.guacd.startupProbe) . | nindent 12 }}
resources:
{{- include "patchmon.guacd.effectiveResources" .Values.patchmon.guacd.resources | nindent 12 }}
volumeMounts:
- name: guacd-tmp
mountPath: /tmp
{{- end }}
restartPolicy: Always
volumes:
- name: tmp
emptyDir: {}
{{- if .Values.patchmon.guacd.enabled }}
- name: guacd-tmp
emptyDir: {}
{{- end }}