Helm Charts / Detect changed charts (push) Successful in 22s
Helm Charts / Library charts tool (push) Skipped
Helm Charts / Application charts crowdsec (push) Skipped
Helm Charts / Application charts hashicorp-vault (push) Skipped
Helm Charts / Application charts minio (push) Skipped
Helm Charts / Application charts pgbouncer (push) Skipped
Helm Charts / Application charts pgcat (push) Skipped
Helm Charts / Application charts prometheus (push) Skipped
Helm Charts / Application charts redis (push) Skipped
Helm Charts / Application charts loki (push) Successful in 14s
Helm Charts / Application charts chart (push) Successful in 34s
Helm Charts / Application charts grafana (push) Failing after 49s
Helm Charts / Application charts alloy (push) Successful in 52s
Alloy plutôt que Promtail, que l'amont a marqué déprécié. Rétention 30 jours, calée sur 305 Mio/jour mesurés — et la mesure a révélé pire que ce que l'issue supposait : traefik ne gardait que 10 minutes d'histoire, clickhouse 2. La propriété tient, éprouvée : jeton écrit, pod supprimé, `kubectl logs` ne répond plus, Loki rend encore les lignes. Ferme #38. Co-Authored-By: Claude Opus 5 <[email protected]> Co-authored-by: Gabriel Radureau <[email protected]>
344 lines
16 KiB
YAML
344 lines
16 KiB
YAML
---
|
|
# template source: https://github.com/bretfisher/docker-build-workflow/blob/main/templates/call-docker-build.yaml
|
|
name: Helm Charts
|
|
|
|
# Celui-ci travaille SEUL (pas d'auth Vault, pas d'apply) : on le garde
|
|
# automatique. Mais `push` sur TOUTES les branches + `pull_request` faisait
|
|
# partir DEUX runs pour le même commit dès qu'une branche avait une PR.
|
|
#
|
|
# Même forme que la CI de kadans : la branche est couverte par `pull_request`,
|
|
# `main` par le `push` d'après-merge. Un run par événement, aucun angle mort.
|
|
#
|
|
# (Le filtre de chemins d'origine, resté en commentaire des années sous un
|
|
# « gitea don't handle well the paths filter », n'était probablement pas en
|
|
# cause : il passait par une ancre YAML, et le parseur d'événements de Gitea ne
|
|
# les résout pas — issues 113 → 117 de kadans. Le job `filter-chart` fait déjà
|
|
# ce tri au niveau job, donc on n'y retouche pas.)
|
|
#
|
|
# ⚠ Chaque clé porte un CORPS explicite : un `pull_request:` nu (valeur nulle)
|
|
# n'est pas une forme éprouvée sur ce Gitea, et son mode d'échec est le
|
|
# silencieux — aucun run, aucune erreur. On copie la forme qui tourne (kadans
|
|
# ci.yml), listes dupliquées à la main, sans ancre.
|
|
on:
|
|
workflow_dispatch: {}
|
|
push:
|
|
branches: [main]
|
|
paths-ignore:
|
|
- '**.md'
|
|
pull_request:
|
|
paths-ignore:
|
|
- '**.md'
|
|
|
|
# cancel any previously-started, yet still active runs of this workflow on the same branch
|
|
concurrency:
|
|
group: ${{ github.ref }}-${{ github.workflow }}
|
|
cancel-in-progress: true
|
|
|
|
.helm_install_dependencies_sh: &helm_install_dependencies_sh |-
|
|
# ⚠⚠ MESURÉ le 2026-09-07 (run 6728, étape « RELEVÉ ») : ce runner NE RÉSOUT
|
|
# PAS `gitea.arcodange.lab` — `getent` code 2, `curl` code 6 — alors qu'il
|
|
# résout parfaitement les registres publics. SEPT charts sur dix déclarent le
|
|
# registre Helm interne en dépendance (crowdsec, grafana, hashicorp-vault,
|
|
# pgbouncer, pgcat, prometheus, redis) : depuis que #32 a énuméré les charts,
|
|
# leurs jobs mouraient TOUS ici, en 7 s, avant la moindre étape utile — et le
|
|
# premier à en faire les frais est le premier à avoir été construit.
|
|
#
|
|
# Gitea donne au job sa PROPRE adresse joignable dans `GITHUB_SERVER_URL`
|
|
# (mesuré : http://192.168.65.254:43000). On ne s'en sert qu'en REPLI, et
|
|
# seulement après avoir constaté que le nom d'origine ne répond pas : rien ne
|
|
# change sur un runner qui, lui, sait le résoudre. Lecture anonyme du registre
|
|
# vérifiée (HTTP 200), donc aucun jeton n'entre ici.
|
|
FORGE_INTERNE='https://gitea.arcodange.lab'
|
|
|
|
joignable() { curl -sf -o /dev/null -m 15 "$1"; }
|
|
|
|
helm_install_dependencies() {
|
|
chart_file="$1/Chart.yaml"
|
|
[[ ! -f "$chart_file" ]] && echo "Chart.yaml not found in $1" && return 1
|
|
|
|
yq eval '.dependencies[]' "$chart_file" -o=json | jq -c '.' | while IFS= read -r dep; do
|
|
name=$(jq -r '.name' <<< "$dep")
|
|
version=$(jq -r '.version' <<< "$dep")
|
|
repo=$(jq -r '.repository' <<< "$dep")
|
|
|
|
en_repli=non
|
|
if ! joignable "${repo}/index.yaml"; then
|
|
repli="${repo/$FORGE_INTERNE/${GITHUB_SERVER_URL:-$FORGE_INTERNE}}"
|
|
if [[ "$repli" != "$repo" ]] && joignable "${repli}/index.yaml"; then
|
|
echo " ⚠ ${repo} injoignable depuis ce runner — repli sur ${repli}"
|
|
repo="$repli"
|
|
en_repli=oui
|
|
fi
|
|
fi
|
|
|
|
url=$(curl -s "${repo}/index.yaml" | yq eval ".entries.${name}[] | select(.version == \"${version}\") | .urls[0]" -)
|
|
# L'index du registre interne publie des URLs ABSOLUES vers le même nom
|
|
# d'hôte : le repli doit les suivre, sinon on retombe dans le code 6.
|
|
# ⚠ On ne réécrit QUE si l'on est effectivement en repli — sur un runner
|
|
# sain, le chemin d'origine reste le chemin d'origine.
|
|
# ⚠ Un `[[ … ]] && …` ici serait un piège : sous `set -e`, la liste rend
|
|
# non nul quand la condition est fausse, et le script AVORTE.
|
|
if [[ "$en_repli" = oui ]]; then
|
|
url="${url/$FORGE_INTERNE/$GITHUB_SERVER_URL}"
|
|
fi
|
|
|
|
echo "Dependency: $name, Version: $version, URL: $url"
|
|
mkdir -p "$1/charts" && curl -sL "$url" -o "$1/charts/${name}-${version}.tgz"
|
|
done
|
|
}
|
|
helm_install_dependencies $chart
|
|
|
|
|
|
jobs:
|
|
filter-chart:
|
|
name: Detect changed charts
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
library_charts: ${{steps.filter-charts.outputs.library_charts}}
|
|
application_charts: ${{steps.filter-charts.outputs.application_charts}}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Get changed files
|
|
id: changed-files
|
|
uses: tj-actions/changed-files@v45
|
|
|
|
- name: Filter modified charts
|
|
id: filter-charts
|
|
run: |
|
|
echo "Changed files:"
|
|
echo "${{ steps.changed-files.outputs.all_changed_files }}"
|
|
|
|
# Find unique directories that contain Chart.yaml among the changed files
|
|
modified_dirs=$(echo "${{ steps.changed-files.outputs.all_changed_files }}" | tr ' ' '\n' | xargs -n1 dirname | sort -u || true)
|
|
|
|
# Initialize an array to store directories that contain Chart.yaml
|
|
helm_chart_dirs=()
|
|
|
|
# Function to find the closest directory containing Chart.yaml
|
|
find_chart_root() {
|
|
dir="$1"
|
|
while [[ "$dir" != "/" && "$dir" != "." ]]; do
|
|
if [[ -f "$dir/Chart.yaml" ]]; then
|
|
echo "$dir"
|
|
return
|
|
fi
|
|
dir=$(dirname "$dir")
|
|
done
|
|
}
|
|
|
|
# Iterate over each modified directory and find the root chart directory
|
|
for dir in $modified_dirs; do
|
|
chart_dir=$(find_chart_root "$dir")
|
|
if [[ -n "$chart_dir" && ! " ${helm_chart_dirs[*]} " =~ " ${chart_dir} " ]]; then
|
|
helm_chart_dirs+=("$chart_dir")
|
|
fi
|
|
done
|
|
|
|
# Initialize arrays for library and application charts
|
|
library_dirs=()
|
|
application_dirs=()
|
|
|
|
# Iterate over each modified directory and check the 'type' field in Chart.yaml
|
|
for dir in ${helm_chart_dirs[@]}; do
|
|
chart_type=$(yq eval '.type' "$dir/Chart.yaml" || echo "undefined")
|
|
|
|
# Add directories to corresponding arrays based on the 'type'
|
|
if [[ "$chart_type" == "library" ]]; then
|
|
library_dirs+=("$dir")
|
|
elif [[ "$chart_type" == "application" ]]; then
|
|
application_dirs+=("$dir")
|
|
fi
|
|
done
|
|
|
|
# Convert the arrays to JSON format
|
|
library_json=$(printf '%s\n' "${library_dirs[@]}" | jq -R . | jq -cs 'map(select(. != ""))')
|
|
application_json=$(printf '%s\n' "${application_dirs[@]}" | jq -R . | jq -cs 'map(select(. != ""))')
|
|
|
|
# Output the JSON arrays
|
|
echo "Modified Helm library charts directories: $library_json"
|
|
echo "library_charts=$library_json" >> $GITHUB_OUTPUT
|
|
|
|
echo "Modified Helm application charts directories: $application_json"
|
|
echo "application_charts=$application_json" >> $GITHUB_OUTPUT
|
|
|
|
library-charts: &charts-matrix-job
|
|
name: Library charts ${{ matrix.chart }}
|
|
runs-on: ubuntu-latest
|
|
needs: filter-chart
|
|
strategy:
|
|
matrix:
|
|
# ⚠⚠ ÉNUMÉRATION EXHAUSTIVE, ET ELLE DOIT LE RESTER.
|
|
#
|
|
# Gitea ne sait pas monter une matrice DYNAMIQUE : on ne peut pas
|
|
# écrire `fromJson(needs.filter-chart.outputs.library_charts)` ici.
|
|
# La conséquence est passée inaperçue longtemps — le détecteur
|
|
# calculait la bonne réponse, et PERSONNE NE LA CONSOMMAIT : le `if:`
|
|
# ci-dessous vérifie seulement si le chart CODÉ EN DUR figure dans la
|
|
# liste détectée. Seuls `tool` et `pgcat` pouvaient donc être
|
|
# construits ; les HUIT AUTRES charts de ce dépôt ne l'étaient jamais,
|
|
# et leurs PR affichaient vert. Mesuré le 2026-09-02 sur la PR #32,
|
|
# qui réécrivait le chart `minio` en entier : `Detect changed charts`
|
|
# ✓, les deux jobs de construction `Skipped`, statut global `success`.
|
|
#
|
|
# Le remède n'a pas besoin de matrice dynamique : le `if:` filtre DÉJÀ
|
|
# sur la sortie du détecteur, donc un chart non modifié reste `Skipped`.
|
|
# Il ne manquait que l'ÉNUMÉRATION.
|
|
#
|
|
# ⚠ Ajouter un chart au dépôt SANS l'ajouter ici le rend invisible à la
|
|
# CI, en silence. C'est le mode d'échec de ce fichier.
|
|
chart: [tool]
|
|
# chart: ${{ fromJson(needs.filter-chart.outputs.library_charts) }}
|
|
type: [library]
|
|
if: >-
|
|
${{
|
|
always() && !contains(needs.*.result, 'failure') && needs.filter-chart.result == 'success'
|
|
&& (
|
|
contains(fromJson(needs.filter-chart.outputs.library_charts), matrix.chart)
|
|
|| contains(fromJson(needs.filter-chart.outputs.application_charts), matrix.chart)
|
|
)
|
|
&& (
|
|
contains(fromJSON('["","pull_request"]'), github.event_name)
|
|
|| github.ref == 'refs/heads/main'
|
|
) }}
|
|
env:
|
|
chart: ${{ matrix.chart }}
|
|
steps:
|
|
|
|
- uses: actions/checkout@v4
|
|
|
|
# ⚠⚠ UNE GATE QUI N'EST CÂBLÉE NULLE PART N'EXISTE PAS (tools#36) — et une
|
|
# gate placée APRÈS une étape qui échoue toujours n'existe pas non plus :
|
|
# elle sort `Skipped`, ni rouge ni absente, rien ne dépasse. Le banc du
|
|
# veilleur passe donc AVANT le téléchargement des dépendances, qui a besoin
|
|
# du réseau et du registre Helm interne. Il n'a besoin d'aucun des deux :
|
|
# stdlib Python seule, plus `yq`/`jq` déjà présents.
|
|
- name: Banc du veilleur (chart prometheus)
|
|
if: >-
|
|
${{
|
|
matrix.chart == 'prometheus'
|
|
&& (
|
|
contains(fromJSON('["","pull_request"]'), github.event_name)
|
|
|| github.ref != 'refs/heads/main'
|
|
)
|
|
}}
|
|
run: |
|
|
set -euo pipefail
|
|
python3 -m unittest discover -s prometheus/veilleur -v
|
|
|
|
# Le témoin toujours allumé ne doit JAMAIS partir sur Telegram : sans
|
|
# sa route vers le récepteur vide il sonnerait toutes les 3 h, et un
|
|
# témoin qui sonne est un témoin qu'on coupe.
|
|
yq -o=json eval '.prometheus.alertmanager.config' prometheus/values.yaml > /tmp/am.json
|
|
jq -e '[.route.routes[] | select(.matchers[0] == "alertname=\"Veilleur\"") | .receiver] == ["neant"]' /tmp/am.json
|
|
jq -e '[.receivers[] | select(.name == "neant") | keys] == [["name"]]' /tmp/am.json
|
|
|
|
# ⚠ RELEVÉ, PAS UNE GATE — il ne peut pas faire échouer le job. Le premier
|
|
# run du job `Application charts prometheus` (le tout premier depuis que
|
|
# #32 a énuméré les charts) est mort ICI, `curl` code 6 : le runner ne
|
|
# RÉSOUT PAS l'hôte de la dépendance. Sept charts sur dix dépendent du
|
|
# registre Helm interne `gitea.arcodange.lab` — donc sept jobs sur dix ne
|
|
# peuvent pas passer, quel que soit leur contenu. Ce relevé nomme lequel
|
|
# des deux hôtes tombe, pour que la prochaine session n'ait pas à le
|
|
# deviner. À retirer quand le trou est bouché.
|
|
- name: RELEVÉ — ce que le runner sait résoudre
|
|
run: |
|
|
set +e
|
|
echo "GITHUB_SERVER_URL=${GITHUB_SERVER_URL:-<vide>}"
|
|
for hote in gitea.arcodange.lab prometheus-community.github.io; do
|
|
echo "--- $hote ---"
|
|
getent hosts "$hote" || echo " PAS DE RÉSOLUTION (getent code $?)"
|
|
curl -sS -o /dev/null -m 10 -w " http=%{http_code}\n" "https://$hote/" \
|
|
|| echo " curl code $?"
|
|
done
|
|
exit 0
|
|
|
|
- run: *helm_install_dependencies_sh
|
|
|
|
- name: Install Helm for test
|
|
if: >-
|
|
${{
|
|
matrix.type != 'library'
|
|
&& (
|
|
contains(fromJSON('["","pull_request"]'), github.event_name)
|
|
|| github.ref != 'refs/heads/main'
|
|
)
|
|
}}
|
|
uses: azure/setup-helm@v4
|
|
- name: Helm template
|
|
if: >-
|
|
${{
|
|
matrix.type != 'library'
|
|
&& (
|
|
contains(fromJSON('["","pull_request"]'), github.event_name)
|
|
|| github.ref != 'refs/heads/main'
|
|
)
|
|
}}
|
|
run: helm template $chart --debug
|
|
|
|
- name: publish ${{ matrix.chart }} helm chart
|
|
if: ${{ contains(fromJSON('["","push"]'), github.event_name) && github.ref == 'refs/heads/main' }}
|
|
run: |
|
|
set -x
|
|
chart=${chart:-tool}
|
|
chart_version=`yq eval .version ${chart}/Chart.yaml`
|
|
chart_package=${chart}-${chart_version}.tgz
|
|
# helm package ${chart}
|
|
tar -X ${chart}/.helmignore -czf ${chart_package} ${chart}
|
|
|
|
# ⚠⚠ LE MÊME REPLI QUE POUR LA DESCENTE DES DÉPENDANCES, ET C'EST
|
|
# POURQUOI CE COMMENTAIRE EXISTE. Le correctif du 2026-09-07 n'avait
|
|
# posé le repli QUE sur `helm_install_dependencies` ; celui-ci est
|
|
# resté sur le nom d'origine, et `main` est devenu rouge dès la
|
|
# fusion suivante — `curl` code 6, « couldn't resolve host ».
|
|
# **Un remède qui vit à DEUX endroits ne se propage pas par la
|
|
# vigilance.** Si un troisième appel au registre apparaît un jour, il
|
|
# a besoin de ces trois lignes lui aussi.
|
|
FORGE_INTERNE='https://gitea.arcodange.lab'
|
|
registre="${FORGE_INTERNE}/api/packages/${{ github.repository_owner }}/helm"
|
|
if ! curl -sf -o /dev/null -m 15 "${registre}/index.yaml"; then
|
|
repli="${registre/$FORGE_INTERNE/${GITHUB_SERVER_URL:-$FORGE_INTERNE}}"
|
|
if [[ "$repli" != "$registre" ]] && curl -sf -o /dev/null -m 15 "${repli}/index.yaml"; then
|
|
echo " ⚠ ${registre} injoignable depuis ce runner — repli sur ${repli}"
|
|
registre="$repli"
|
|
fi
|
|
fi
|
|
|
|
# ⚠ ICI LE JETON PART, alors que la descente est anonyme. Le repli est
|
|
# `http://192.168.65.254:43000` — la passerelle Docker de l'hôte, en
|
|
# clair. Ce n'est pas une exposition NEUVE : `actions/checkout` pose
|
|
# déjà son `http.<cette-url>.extraheader` porteur du jeton de forge à
|
|
# chaque job. Même chemin, même machine, même classe de risque.
|
|
|
|
# ⚠⚠ ET L'APPEL DOIT POUVOIR ÉCHOUER. `curl` rend 0 sur un 401 comme
|
|
# sur un 409 : l'étape passait au vert en ne publiant rien. On relève
|
|
# donc le CODE HTTP (`-w`) et on en décide — le code de sortie de
|
|
# `curl`, lui, ne parle que du transport. Mesuré le 2026-09-07 : le
|
|
# registre ne contenait que `minio 0.2.0` et `tool 0.1.0`, deux
|
|
# charts sur dix, sans que rien n'ait jamais rougi.
|
|
# ⚠ `409` est ACCEPTÉ et nommé : republier une version inchangée n'est
|
|
# pas une faute, c'est le cas normal d'un lot qui ne touche pas au
|
|
# `version:` du chart. Tout autre code arrête le job.
|
|
code=$(curl -s -o /tmp/publication.out -w '%{http_code}' \
|
|
--user ${{ github.actor }}:${{ secrets.PACKAGES_TOKEN }} \
|
|
-X POST --upload-file ./${chart_package} \
|
|
"${registre}/api/charts") || { echo "✖ curl a échoué (code $?) — le registre est injoignable"; exit 1; }
|
|
cat /tmp/publication.out || true
|
|
case "$code" in
|
|
2??) echo "✓ ${chart_package} publié (HTTP $code)" ;;
|
|
409) echo "✓ ${chart_package} déjà présent au registre (HTTP 409) — rien à republier" ;;
|
|
*) echo "✖ publication REFUSÉE (HTTP $code) — le chart n'est PAS au registre"; exit 1 ;;
|
|
esac
|
|
|
|
application-charts:
|
|
<<: *charts-matrix-job
|
|
name: Application charts ${{ matrix.chart }}
|
|
needs: [filter-chart,library-charts]
|
|
strategy:
|
|
matrix:
|
|
# ⚠ Les ONZE charts applicatifs du dépôt — voir l'avertissement du job
|
|
# `library-charts` ci-dessus. Un chart absent de cette liste n'est
|
|
# JAMAIS construit, et sa PR est verte quand même.
|
|
# chart: ${{ fromJson(needs.filter-chart.outputs.application_charts) }}
|
|
chart: [alloy, chart, crowdsec, grafana, hashicorp-vault, loki, minio, pgbouncer, pgcat, prometheus, redis]
|
|
type: [application] |