Helm Charts / Detect changed charts (push) Successful in 20s
Helm Charts / Library charts tool (push) Skipped
Helm Charts / Application charts chart (push) Skipped
Helm Charts / Application charts crowdsec (push) Skipped
Helm Charts / Application charts grafana (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
Le repli DNS était posé sur la descente des dépendances seulement ; la publication restait sur le nom d'origine, d'où le code 6 qui a rougi main. Et le second défaut, muet : curl rend 0 sur un 401 comme sur un 409, donc l'étape passait au vert en ne publiant rien. Le registre ne contenait que deux charts sur dix, sans que rien n'ait jamais rougi. 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 NEUF 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: [chart, crowdsec, grafana, hashicorp-vault, minio, pgbouncer, pgcat, prometheus, redis]
|
|
type: [application] |