helm: add helm chart
pipeline / lint-and-format (push) Successful in 4m1s
pipeline / build-and-push-images (push) Successful in 2m8s
pipeline / lint-and-publish-chart (push) Successful in 11s

This commit is contained in:
2026-07-26 16:12:17 +02:00
parent d878af6d1a
commit ff9d88d582
15 changed files with 622 additions and 1 deletions
+45
View File
@@ -76,3 +76,48 @@ jobs:
tags: |
git.vhaudiquet.fr/vhaudiquet/lolstats-match_collector:latest
git.vhaudiquet.fr/vhaudiquet/lolstats-match_collector:${{ github.sha }}
lint-and-publish-chart:
runs-on: ubuntu-latest
needs: build-and-push-images
steps:
- name: Checkout repository
uses: https://gitea.com/actions/checkout@v4
with:
fetch-depth: 0
- name: Install Helm
uses: azure/setup-helm@v5
- name: Update Chart version and appVersion
run: |
# Get base chart version from Chart.yaml
BASE_VERSION=$(grep '^version:' helm/buildpath/Chart.yaml | awk '{print $2}')
# Use timestamp for ordered versions (e.g., 0.1.0-20240511-130400)
# Note: SemVer pre-release cannot contain dots, only hyphens and alphanumerics
TIMESTAMP=$(date -u +"%Y%m%d-%H%M%S")
CHART_VERSION="${BASE_VERSION}-${TIMESTAMP}"
# Update both version and appVersion
sed -i "s/^version:.*/version: \"${CHART_VERSION}\"/" helm/buildpath/Chart.yaml
sed -i "s/^appVersion:.*/appVersion: \"${{ github.sha }}\"/" helm/buildpath/Chart.yaml
echo "chart_version=${CHART_VERSION}" >> $GITHUB_ENV
- name: Lint Helm chart
run: helm lint helm/buildpath/
- name: Package Helm chart
run: |
helm package helm/buildpath/ \
--version "${{ env.chart_version }}" \
--app-version "${{ github.sha }}"
- name: Push Helm chart to Gitea registry
run: |
echo "Uploading chart to Gitea registry..."
echo "User: ${{ github.actor }}"
echo "URL: https://git.vhaudiquet.fr/api/packages/vhaudiquet/helm/api/charts"
# Trim any trailing newline from the token
TOKEN=$(echo -n "${{ secrets.PACKAGES_TOKEN }}" | tr -d '\n')
curl --fail -v --user "${{ github.actor }}:${TOKEN}" -X POST --upload-file buildpath-*.tgz "https://git.vhaudiquet.fr/api/packages/vhaudiquet/helm/api/charts"
+12
View File
@@ -0,0 +1,12 @@
apiVersion: v2
name: buildpath
description: Helm chart for buildpath.win (LoL stats frontend, match collector, MongoDB)
type: application
version: 0.1.0
appVersion: "1.0.0"
keywords:
- lol
- stats
- buildpath
maintainers:
- name: vhaudiquet
+11
View File
@@ -0,0 +1,11 @@
buildpath chart installed.
Check status:
kubectl -n {{ .Release.Namespace }} get all -l app.kubernetes.io/instance={{ .Release.Name }}
Mongo host seen by app pods: {{ include "buildpath.mongoHost" . }}
Frontend host (ingress): {{ .Values.frontend.ingress.host }}
If this is a brand-new install, rotate the default credentials in your values before exposing publicly:
secrets.riotApiKey
mongo.auth.rootPassword
+55
View File
@@ -0,0 +1,55 @@
{{/* Expand the name of the chart. */}}
{{- define "buildpath.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/* Create a default fully qualified app name. */}}
{{- define "buildpath.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/* Chart name and version label. */}}
{{- define "buildpath.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/* Common labels. */}}
{{- define "buildpath.labels" -}}
helm.sh/chart: {{ include "buildpath.chart" . }}
{{ include "buildpath.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/* Selector labels. */}}
{{- define "buildpath.selectorLabels" -}}
app.kubernetes.io/name: {{ include "buildpath.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/* Selector labels for a specific component. */}}
{{- define "buildpath.componentSelectorLabels" -}}
app.kubernetes.io/name: {{ include "buildpath.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: {{ .component | quote }}
{{- end }}
{{/* MongoDB hostname (service DNS). */}}
{{- define "buildpath.mongoHost" -}}
{{- if .Values.mongo.hostOverride -}}
{{- .Values.mongo.hostOverride -}}
{{- else -}}
{{- printf "%s-mongo" (include "buildpath.fullname" .) -}}
{{- end -}}
{{- end }}
+16
View File
@@ -0,0 +1,16 @@
{{- if .Values.cdragon.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "buildpath.fullname" . }}-cdragon
labels:
{{- include "buildpath.labels" . | nindent 4 }}
app.kubernetes.io/component: cdragon-cache
spec:
accessModes:
- {{ .Values.cdragon.volume.accessMode | quote }}
storageClassName: {{ .Values.cdragon.volume.storageClassName | quote }}
resources:
requests:
storage: {{ .Values.cdragon.volume.size | quote }}
{{- end }}
+15
View File
@@ -0,0 +1,15 @@
{{- if or .Values.config.extraEnv (and .Values.cdragon.enabled .Values.cdragon.cacheDir) }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "buildpath.fullname" . }}
labels:
{{- include "buildpath.labels" . | nindent 4 }}
data:
{{- if .Values.cdragon.enabled }}
CDRAGON_CACHE_DIR: {{ .Values.cdragon.cacheDir | quote }}
{{- end }}
{{- range $k, $v := .Values.config.extraEnv }}
{{ $k }}: {{ $v | quote }}
{{- end }}
{{- end }}
@@ -0,0 +1,53 @@
{{- if .Values.frontend.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "buildpath.fullname" . }}-frontend
labels:
{{- include "buildpath.labels" . | nindent 4 }}
app.kubernetes.io/component: frontend
spec:
replicas: {{ .Values.frontend.replicaCount }}
selector:
matchLabels:
{{- include "buildpath.componentSelectorLabels" (set . "component" "frontend") | nindent 6 }}
template:
metadata:
labels:
{{- include "buildpath.componentSelectorLabels" (set . "component" "frontend") | nindent 8 }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: frontend
image: "{{ .Values.global.imageRegistry }}/{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}"
imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
ports:
- name: http
containerPort: 3000
protocol: TCP
envFrom:
- secretRef:
name: {{ include "buildpath.fullname" . }}
- configMapRef:
name: {{ include "buildpath.fullname" . }}
{{- with .Values.frontend.extraEnv }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if .Values.cdragon.enabled }}
volumeMounts:
- name: cdragon
mountPath: {{ .Values.cdragon.cacheDir | quote }}
{{- end }}
resources:
{{- toYaml .Values.frontend.resources | nindent 12 }}
{{- if .Values.cdragon.enabled }}
volumes:
- name: cdragon
persistentVolumeClaim:
claimName: {{ include "buildpath.fullname" . }}-cdragon
{{- end }}
{{- end }}
@@ -0,0 +1,36 @@
{{- if and .Values.frontend.enabled .Values.frontend.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "buildpath.fullname" . }}-frontend
labels:
{{- include "buildpath.labels" . | nindent 4 }}
app.kubernetes.io/component: frontend
{{- with .Values.frontend.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.frontend.ingress.className }}
ingressClassName: {{ .Values.frontend.ingress.className | quote }}
{{- end }}
{{- if .Values.frontend.ingress.tls.enabled }}
tls:
- hosts:
- {{ .Values.frontend.ingress.host | quote }}
{{- if .Values.frontend.ingress.tls.secretName }}
secretName: {{ .Values.frontend.ingress.tls.secretName | quote }}
{{- end }}
{{- end }}
rules:
- host: {{ .Values.frontend.ingress.host | quote }}
http:
paths:
- path: {{ .Values.frontend.ingress.path | quote }}
pathType: {{ .Values.frontend.ingress.pathType }}
backend:
service:
name: {{ include "buildpath.fullname" . }}-frontend
port:
number: {{ .Values.frontend.service.port }}
{{- end }}
@@ -0,0 +1,18 @@
{{- if .Values.frontend.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "buildpath.fullname" . }}-frontend
labels:
{{- include "buildpath.labels" . | nindent 4 }}
app.kubernetes.io/component: frontend
spec:
type: {{ .Values.frontend.service.type }}
ports:
- name: http
port: {{ .Values.frontend.service.port }}
targetPort: http
protocol: TCP
selector:
{{- include "buildpath.componentSelectorLabels" (set . "component" "frontend") | nindent 4 }}
{{- end }}
@@ -0,0 +1,63 @@
{{- if .Values.matchCollector.enabled }}
apiVersion: batch/v1
kind: CronJob
metadata:
name: {{ include "buildpath.fullname" . }}-match-collector
labels:
{{- include "buildpath.labels" . | nindent 4 }}
app.kubernetes.io/component: match-collector
spec:
schedule: {{ .Values.matchCollector.schedule | quote }}
concurrencyPolicy: {{ .Values.matchCollector.concurrencyPolicy }}
successfulJobsHistoryLimit: {{ .Values.matchCollector.successfulJobsHistoryLimit }}
failedJobsHistoryLimit: {{ .Values.matchCollector.failedJobsHistoryLimit }}
jobTemplate:
spec:
backoffLimit: {{ .Values.matchCollector.backoffLimit }}
{{- if .Values.matchCollector.activeDeadlineSeconds }}
activeDeadlineSeconds: {{ .Values.matchCollector.activeDeadlineSeconds }}
{{- end }}
template:
metadata:
labels:
{{- include "buildpath.componentSelectorLabels" (set . "component" "match-collector") | nindent 12 }}
spec:
restartPolicy: {{ .Values.matchCollector.jobRestartPolicy }}
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 12 }}
{{- end }}
containers:
- name: match-collector
image: "{{ .Values.global.imageRegistry }}/{{ .Values.matchCollector.image.repository }}:{{ .Values.matchCollector.image.tag }}"
imagePullPolicy: {{ .Values.matchCollector.image.pullPolicy }}
# Override the image CMD: run the collector exactly once, then exit.
# The image ENTRYPOINT (docker-entrypoint.sh) still fixes /cdragon perms
# and drops privileges to the node user via su-exec.
command:
- /bin/sh
- -c
- node --import=tsx src/index.ts
envFrom:
- secretRef:
name: {{ include "buildpath.fullname" . }}
- configMapRef:
name: {{ include "buildpath.fullname" . }}
{{- with .Values.matchCollector.extraEnv }}
env:
{{- toYaml . | nindent 16 }}
{{- end }}
{{- if .Values.cdragon.enabled }}
volumeMounts:
- name: cdragon
mountPath: {{ .Values.cdragon.cacheDir | quote }}
{{- end }}
resources:
{{- toYaml .Values.matchCollector.resources | nindent 16 }}
{{- if .Values.cdragon.enabled }}
volumes:
- name: cdragon
persistentVolumeClaim:
claimName: {{ include "buildpath.fullname" . }}-cdragon
{{- end }}
{{- end }}
@@ -0,0 +1,16 @@
{{- if .Values.mongo.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "buildpath.fullname" . }}-mongo-config
labels:
{{- include "buildpath.labels" . | nindent 4 }}
app.kubernetes.io/component: mongo
spec:
accessModes:
- {{ .Values.mongo.configVolume.accessMode | quote }}
storageClassName: {{ .Values.mongo.configVolume.storageClassName | quote }}
resources:
requests:
storage: {{ .Values.mongo.configVolume.size | quote }}
{{- end }}
@@ -0,0 +1,18 @@
{{- if .Values.mongo.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "buildpath.fullname" . }}-mongo
labels:
{{- include "buildpath.labels" . | nindent 4 }}
app.kubernetes.io/component: mongo
spec:
type: {{ .Values.mongo.service.type }}
ports:
- name: mongo
port: {{ .Values.mongo.service.port }}
targetPort: mongo
protocol: TCP
selector:
{{- include "buildpath.componentSelectorLabels" (set . "component" "mongo") | nindent 4 }}
{{- end }}
@@ -0,0 +1,79 @@
{{- if .Values.mongo.enabled }}
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "buildpath.fullname" . }}-mongo
labels:
{{- include "buildpath.labels" . | nindent 4 }}
app.kubernetes.io/component: mongo
spec:
serviceName: {{ include "buildpath.fullname" . }}-mongo
replicas: 1
selector:
matchLabels:
{{- include "buildpath.componentSelectorLabels" (set . "component" "mongo") | nindent 6 }}
template:
metadata:
labels:
{{- include "buildpath.componentSelectorLabels" (set . "component" "mongo") | nindent 8 }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
runAsUser: {{ .Values.mongo.runAsUser }}
runAsGroup: {{ .Values.mongo.runAsGroup }}
fsGroup: {{ .Values.mongo.runAsGroup }}
containers:
- name: mongo
image: "{{ .Values.mongo.image.repository }}:{{ .Values.mongo.image.tag }}"
imagePullPolicy: {{ .Values.mongo.image.pullPolicy }}
args:
- mongod
- --wiredTigerCacheSizeGB
- {{ .Values.mongo.wiredTigerCacheSizeGB | quote }}
env:
- name: MONGO_INITDB_ROOT_USERNAME
valueFrom:
secretKeyRef:
name: {{ include "buildpath.fullname" . }}
key: MONGO_USER
- name: MONGO_INITDB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "buildpath.fullname" . }}
key: MONGO_PASS
{{- if .Values.mongo.auth.initialDatabase }}
- name: MONGO_INITDB_DATABASE
value: {{ .Values.mongo.auth.initialDatabase | quote }}
{{- end }}
{{- with .Values.mongo.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- name: mongo
containerPort: 27017
protocol: TCP
volumeMounts:
- name: data
mountPath: /data/db
- name: config
mountPath: /data/configdb
resources:
{{- toYaml .Values.mongo.resources | nindent 12 }}
volumes:
- name: config
persistentVolumeClaim:
claimName: {{ include "buildpath.fullname" . }}-mongo-config
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- {{ .Values.mongo.dataVolume.accessMode | quote }}
storageClassName: {{ .Values.mongo.dataVolume.storageClassName | quote }}
resources:
requests:
storage: {{ .Values.mongo.dataVolume.size | quote }}
{{- end }}
+21
View File
@@ -0,0 +1,21 @@
{{- if .Values.secrets.riotApiKey }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "buildpath.fullname" . }}
labels:
{{- include "buildpath.labels" . | nindent 4 }}
type: Opaque
stringData:
RIOT_API_KEY: {{ .Values.secrets.riotApiKey | quote }}
{{- $mongoUser := .Values.secrets.mongoUser | default .Values.mongo.auth.rootUser }}
{{- $mongoPass := .Values.secrets.mongoPass | default .Values.mongo.auth.rootPassword }}
MONGO_USER: {{ $mongoUser | quote }}
MONGO_PASS: {{ $mongoPass | quote }}
{{- if .Values.mongo.enabled }}
MONGO_HOST: {{ include "buildpath.mongoHost" . | quote }}
MONGO_URI: {{ printf "mongodb://%s:%s@%s:27017/" $mongoUser $mongoPass (include "buildpath.mongoHost" .) | quote }}
{{- else if .Values.mongo.hostOverride }}
MONGO_HOST: {{ .Values.mongo.hostOverride | quote }}
{{- end }}
{{- end }}
+163
View File
@@ -0,0 +1,163 @@
# buildpath Helm chart values
# Default values in https://github.com/buildpath/buildpath-chart
# --- Global / image ---
global:
# Registry used for the app images.
imageRegistry: git.vhaudiquet.fr/vhaudiquet
imagePullSecrets: []
imagePullPolicy: IfNotPresent
# Mutually override the chart name (used in resource names).
nameOverride: ""
fullnameOverride: ""
# --- MongoDB ---
mongo:
enabled: true
image:
repository: mongo
tag: "8.2.11"
pullPolicy: IfNotPresent
# If you run MongoDB outside this chart, set enabled=false and hostOverride
# to the address of your external Mongo instance.
hostOverride: ""
# Credentials. The root user is created on first init of the data volume.
auth:
rootUser: root
rootPassword: "change-me-please"
# Database name created on first init (optional).
initialDatabase: ""
# WiredTiger cache size (--wiredTigerCacheSizeGB)
wiredTigerCacheSizeGB: "2"
# Run as root, mirrors the compose `user: root:root`.
runAsUser: 0
runAsGroup: 0
resources:
limits:
memory: 6Gi
requests:
memory: 3Gi
# Data volume for /data/db (StatefulSet volumeClaimTemplates)
dataVolume:
storageClassName: "longhorn"
accessMode: ReadWriteOnce
size: 20Gi
# Config volume for /data/configdb
configVolume:
storageClassName: "longhorn"
accessMode: ReadWriteOnce
size: 1Gi
service:
type: ClusterIP
port: 27017
# Extra env vars to pass to mongo (optional).
extraEnv: []
# --- Shared CDRagon cache (RWX) ---
cdragon:
enabled: true
cacheDir: /cdragon
# RWX volume shared between match_collector (writer) and frontend (reader).
volume:
storageClassName: "longhorn"
accessMode: ReadWriteMany
size: 5Gi
# --- frontend (Nuxt) ---
frontend:
enabled: true
image:
repository: lolstats-frontend
tag: "latest"
pullPolicy: Always
replicaCount: 1
service:
type: ClusterIP
port: 3000
ingress:
enabled: true
className: "" # empty => cluster default ingressClass (Traefik)
annotations: {}
host: buildpath.win
# Path matching; Traefik by default does prefix matching on the prefixed path,
# but the standard ingress is fine with / => Exact or Prefix.
path: /
pathType: Prefix
tls:
enabled: false
# Secret name to reuse an existing TLS cert. If empty and tls.enabled=true,
# you must provision a cert (e.g. cert-manager is left to you to wire).
secretName: ""
resources:
limits:
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
extraEnv: {}
# --- match_collector ---
# Runs as a CronJob instead of the compose "sleep 12h then run" loop.
matchCollector:
enabled: true
image:
repository: lolstats-match_collector
tag: "latest"
pullPolicy: Always
# Cron schedule in UTC. Default: every 12 hours, mirroring the compose sleep.
schedule: "0 */12 * * *"
# Standard k8s CronJob settings.
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobRestartPolicy: Never
# seconds the Job is allowed to run before being terminated.
activeDeadlineSeconds: 3600
# k8s Job backoffLimit (retries on failure).
backoffLimit: 2
resources:
limits:
memory: 1Gi
requests:
cpu: 200m
memory: 256Mi
extraEnv: {}
# --- Secrets ---
# The Riot API key and Mongo credentials are stored in a single Secret that
# every component mounts as env. Override the values below in your own
# values file / external secret.
secrets:
# Riot Games API key used by match_collector.
riotApiKey: "change-me-please"
# Mongo credentials. They default to the values in mongo.auth above; if you
# want different ones in the Secret, override here.
# These are exposed to frontend + match_collector as MONGO_USER/MONGO_PASS/MONGO_HOST.
mongoUser: ""
mongoPass: ""
# --- Default env vars exposed to frontend and match_collector via a ConfigMap ---
# Anything added here becomes an env var on both pods.
config:
extraEnv: {}
# SOME_VAR: "value"