chore: build
ci-api / test (push) Successful in 23m32s
ci-web / test (push) Successful in 14m7s

This commit is contained in:
Xor290
2026-09-20 18:52:09 +02:00
parent 9e11f01c2c
commit 8d857faa45
66 changed files with 3673 additions and 1564 deletions
@@ -0,0 +1,30 @@
Backend deployed as {{ include "backend.fullname" . }} ({{ .Values.replicaCount }} replica{{ if ne (int .Values.replicaCount) 1 }}s{{ end }}).
{{- if .Values.migrationJob.enabled }}
Migrations (backend/migrations) ran as a pre-install/pre-upgrade hook.
{{- else }}
migrationJob.enabled is false -- you must run backend/migrations against
DATABASE_URL yourself before the API will work against an empty database.
{{- end }}
{{- if .Values.seedJob.enabled }}
The initial admin account was created by the post-install seed Job. Check
its logs for the username (`kubectl logs job/{{ include "backend.fullname" . }}-seed -n {{ .Release.Namespace }}`)
-- it will NOT run again on upgrade, and re-enabling it once the admin
already exists will fail the Job.
{{- end }}
This chart creates no ingress: routing is done by the ingressroute chart
(charts/ingressroute), which claims "/api/" and "/uploads/" for this Service
on the same host as the frontend.
In-cluster, the API is reachable at:
http://{{ include "backend.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}
{{- if eq .Values.env.MEDIA_STORAGE_DRIVER "local" }}
MEDIA_STORAGE_DRIVER=local: uploaded files live on PersistentVolumeClaims
(uploads: {{ if .Values.persistence.uploads.enabled }}{{ .Values.persistence.uploads.size }}{{ else }}DISABLED -- uploads will be lost on pod restart{{ end }},
verification: {{ if .Values.persistence.verification.enabled }}{{ .Values.persistence.verification.size }}{{ else }}DISABLED -- files will be lost on pod restart{{ end }}).
This only works correctly with replicaCount=1 / autoscaling disabled.
Switch to MEDIA_STORAGE_DRIVER=s3 before scaling beyond one replica.
{{- end }}
@@ -0,0 +1,25 @@
{{- define "backend.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "backend.fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "backend.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ include "backend.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "backend.selectorLabels" -}}
app.kubernetes.io/name: {{ include "backend.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "backend.secretName" -}}
{{ include "backend.fullname" . }}-secrets
{{- end }}
@@ -0,0 +1,10 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "backend.fullname" . }}
labels:
{{- include "backend.labels" . | nindent 4 }}
data:
{{- range $k, $v := .Values.env }}
{{ $k }}: {{ $v | quote }}
{{- end }}
@@ -0,0 +1,90 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "backend.fullname" . }}
labels:
{{- include "backend.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
{{- if or .Values.persistence.uploads.enabled .Values.persistence.verification.enabled }}
# Volumes ReadWriteOnce : un rolling update bloquerait (l'ancien Pod garde le
# volume, le nouveau ne peut pas le monter).
strategy:
type: Recreate
{{- end }}
selector:
matchLabels:
{{- include "backend.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "backend.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["/app/api"]
ports:
- name: http
containerPort: {{ .Values.env.PORT | int }}
protocol: TCP
envFrom:
- configMapRef:
name: {{ include "backend.fullname" . }}
- secretRef:
name: {{ include "backend.secretName" . }}
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
{{- if .Values.persistence.uploads.enabled }}
- name: uploads
mountPath: {{ .Values.env.MEDIA_LOCAL_DIR }}
{{- end }}
{{- if .Values.persistence.verification.enabled }}
- name: verification
mountPath: {{ .Values.env.VERIFICATION_UPLOAD_DIR }}
{{- end }}
volumes:
{{- if .Values.persistence.uploads.enabled }}
- name: uploads
persistentVolumeClaim:
claimName: {{ include "backend.fullname" . }}-uploads
{{- end }}
{{- if .Values.persistence.verification.enabled }}
- name: verification
persistentVolumeClaim:
claimName: {{ include "backend.fullname" . }}-verification
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
@@ -0,0 +1,22 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "backend.fullname" . }}
labels:
{{- include "backend.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "backend.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
@@ -0,0 +1,58 @@
{{- if .Values.migrationJob.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "backend.fullname" . }}-migrate-{{ .Release.Revision }}
labels:
{{- include "backend.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
backoffLimit: {{ .Values.migrationJob.backoffLimit }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "backend.name" . }}-migrate
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: migrate
spec:
restartPolicy: Never
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
# Migrations are baked into the backend image (see backend/Dockerfile)
# so they always match the version being deployed; this initContainer
# just hands them to the golang-migrate image below via a shared
# emptyDir, since that image has the `migrate` binary but not our SQL.
initContainers:
- name: copy-migrations
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
command: ["sh", "-c", "cp -r /app/migrations/. /migrations/"]
volumeMounts:
- name: migrations
mountPath: /migrations
containers:
- name: migrate
image: "{{ .Values.migrationJob.image.repository }}:{{ .Values.migrationJob.image.tag }}"
imagePullPolicy: {{ .Values.migrationJob.image.pullPolicy }}
args:
- "-path=/migrations"
- "-database=$(DATABASE_URL)"
- "up"
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "backend.secretName" . }}
key: DATABASE_URL
volumeMounts:
- name: migrations
mountPath: /migrations
readOnly: true
volumes:
- name: migrations
emptyDir: {}
{{- end }}
@@ -0,0 +1,20 @@
{{- range $name, $pvc := .Values.persistence }}
{{- if $pvc.enabled }}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "backend.fullname" $ }}-{{ $name }}
labels:
{{- include "backend.labels" $ | nindent 4 }}
spec:
accessModes:
- {{ $pvc.accessMode }}
{{- if $pvc.storageClass }}
storageClassName: {{ $pvc.storageClass }}
{{- end }}
resources:
requests:
storage: {{ $pvc.size }}
{{- end }}
{{- end }}
@@ -0,0 +1,32 @@
{{- if or (not .Values.secrets.JWT_SECRET) (eq .Values.secrets.JWT_SECRET "change-me-to-a-long-random-secret") }}
{{- fail "secrets.JWT_SECRET must be overridden to a strong, unique value (e.g. `openssl rand -base64 48`)" }}
{{- end }}
{{- if and .Values.seedJob.enabled (not .Values.secrets.SEED_ADMIN_PASSWORD) }}
{{- fail "secrets.SEED_ADMIN_PASSWORD is required when seedJob.enabled=true" }}
{{- end }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "backend.secretName" . }}
labels:
{{- include "backend.labels" . | nindent 4 }}
annotations:
# Hook pre-install/pre-upgrade : le Job de migration (hook lui aussi) tourne
# AVANT la création des ressources normales du chart et lit DATABASE_URL
# depuis ce Secret. Sans ce hook, le Job démarrerait sans Secret.
# Conséquence : `helm uninstall` ne supprime pas ce Secret.
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": before-hook-creation
type: Opaque
stringData:
{{- range $k, $v := .Values.secrets }}
{{ $k }}: {{ $v | quote }}
{{- end }}
# Assemblées ici (plutôt que passées telles quelles) pour que
# database.*/redis.* restent des champs structurés côté values.yaml, à
# l'image de postgresql.auth/redis.auth des charts ../postgresql, ../redis
# -- alors que backend/internal/platform/config ne sait lire qu'une seule
# chaîne de connexion.
DATABASE_URL: {{ printf "postgres://%s:%s@%s:%v/%s?sslmode=%s" (.Values.database.user | urlquery) (.Values.secrets.DB_PASSWORD | urlquery) .Values.database.host .Values.database.port .Values.database.name .Values.database.sslmode | quote }}
REDIS_URL: {{ printf "redis://:%s@%s:%v/%v" (.Values.secrets.REDIS_PASSWORD | urlquery) .Values.redis.host .Values.redis.port .Values.redis.db | quote }}
@@ -0,0 +1,40 @@
{{- if .Values.seedJob.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "backend.fullname" . }}-seed
labels:
{{- include "backend.labels" . | nindent 4 }}
annotations:
# post-install only, never post-upgrade: cmd/seed hard-fails if
# SEED_ADMIN_USERNAME already exists (see backend/cmd/seed/main.go), so
# it is not safe to re-run on every upgrade. Turn seedJob.enabled back
# off after the first successful install.
"helm.sh/hook": post-install
"helm.sh/hook-weight": "5"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
backoffLimit: {{ .Values.seedJob.backoffLimit }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "backend.name" . }}-seed
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: seed
spec:
restartPolicy: Never
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: seed
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["/app/seed"]
envFrom:
- configMapRef:
name: {{ include "backend.fullname" . }}
- secretRef:
name: {{ include "backend.secretName" . }}
{{- end }}
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "backend.fullname" . }}
labels:
{{- include "backend.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "backend.selectorLabels" . | nindent 4 }}