chore: update
ci-api / test (push) Successful in 24m53s

This commit is contained in:
Nuxgrid
2026-07-31 13:25:03 +02:00
parent ac8bdbcab7
commit dce80908e3
31 changed files with 740 additions and 155 deletions
@@ -68,12 +68,22 @@ func NewHelmProvisioner(
}, nil
}
// sharedTraefikNamespace / sharedTraefikRelease : le routeur Traefik + WAF
// Coraza est installé UNE SEULE FOIS pour toutes les démos (voir
// EnsureSharedInfra), jamais par démo — chaque démo installe seulement le
// chart "ingressroute" qui la raccorde à ce Traefik partagé.
const (
sharedTraefikNamespace = "traefik"
sharedTraefikRelease = "gestion-traefik"
)
// Provision déploie une démo avec Helm :
// 1. Crée le namespace
// 2. Installe PostgreSQL pour la démo
// 3. Installe Redis pour la démo
// 4. Installe le chart backend
// 5. Installe le chart frontend
// 6. Raccorde la démo au Traefik partagé (WAF + routage HTTPS du sous-domaine)
func (h *HelmProvisioner) Provision(d Demo, resources []ExternalResource) error {
// Créer le namespace
if err := h.createNamespace(d.Namespace); err != nil {
@@ -85,6 +95,7 @@ func (h *HelmProvisioner) Provision(d Demo, resources []ExternalResource) error
redisValues := h.buildRedisValues(d)
backendValues := h.buildBackendValues(d, resources)
frontendValues := h.buildFrontendValues(d)
ingressValues := h.buildIngressRouteValues(d)
// Installer PostgreSQL (nécessaire pour le backend)
if err := h.installChart(d.Namespace, "postgresql", postgresValues); err != nil {
@@ -110,6 +121,13 @@ func (h *HelmProvisioner) Provision(d Demo, resources []ExternalResource) error
return fmt.Errorf("déploiement frontend: %w", err)
}
// Raccorder au Traefik partagé : sans ça la démo n'est ni routée ni
// protégée par le WAF, même si backend/frontend tournent.
if err := h.installChart(d.Namespace, "ingressroute", ingressValues); err != nil {
h.deleteNamespace(d.Namespace)
return fmt.Errorf("déploiement ingressroute: %w", err)
}
// Attendre que les pods soient prêts
if err := h.waitForRollout(d.Namespace); err != nil {
log.Printf("Warning: rollout check échoué pour %s: %v", d.Namespace, err)
@@ -118,6 +136,33 @@ func (h *HelmProvisioner) Provision(d Demo, resources []ExternalResource) error
return nil
}
// EnsureSharedInfra installe (ou met à jour, de façon idempotente) le
// Traefik partagé + WAF Coraza. À appeler une fois au démarrage de l'API,
// avant toute démo — voir cmd/api/main.go. Contrairement à Provision, ceci
// n'est PAS répété par démo.
func (h *HelmProvisioner) EnsureSharedInfra() error {
if err := h.createNamespace(sharedTraefikNamespace); err != nil {
return fmt.Errorf("création namespace %s: %w", sharedTraefikNamespace, err)
}
chartPath := filepath.Join(h.chartsDir, "traefik")
// Le chart traefik dépend du chart officiel traefik/traefik (subchart) :
// il faut le récupérer avant l'install (nécessite un accès internet
// sortant depuis ce pod, comme pour le plugin Coraza téléchargé par
// Traefik lui-même au démarrage).
if err := h.runHelmDependencyUpdate(chartPath); err != nil {
return fmt.Errorf("helm dependency update traefik: %w", err)
}
if err := h.upgradeInstallChart(sharedTraefikNamespace, sharedTraefikRelease, "traefik", nil); err != nil {
return fmt.Errorf("déploiement traefik partagé: %w", err)
}
log.Printf("Traefik partagé (WAF Coraza + rate-limit) prêt dans le namespace %s", sharedTraefikNamespace)
return nil
}
// Teardown détruit une démo en supprimant son namespace (cascading).
func (h *HelmProvisioner) Teardown(d Demo) error {
return h.deleteNamespace(d.Namespace)
@@ -148,42 +193,93 @@ func (h *HelmProvisioner) deleteNamespace(name string) error {
})
}
// installChart installe un chart Helm avec des valeurs personnalisées.
// installChart installe (première install, jamais réappliqué) un chart Helm
// avec des valeurs personnalisées. Release name : "<namespace>-<chartName>".
// Adapté aux charts par démo (namespace toujours neuf).
func (h *HelmProvisioner) installChart(namespace, chartName string, values map[string]interface{}) error {
chartPath := filepath.Join(h.chartsDir, chartName)
// Créer un fichier values.yaml temporaire
valuesFile := filepath.Join("/tmp", fmt.Sprintf("values-%s-%s.yaml", namespace, chartName))
valuesYAML, err := yaml.Marshal(values)
release := fmt.Sprintf("%s-%s", namespace, chartName)
valuesFile, cleanup, err := h.writeValuesFile(namespace, chartName, values)
if err != nil {
return fmt.Errorf("marshal YAML: %w", err)
return err
}
if err := os.WriteFile(valuesFile, valuesYAML, 0644); err != nil {
return fmt.Errorf("write values file: %w", err)
}
defer os.Remove(valuesFile)
defer cleanup()
// Construire la commande helm install
args := []string{
"install",
fmt.Sprintf("%s-%s", namespace, chartName),
chartPath,
release,
filepath.Join(h.chartsDir, chartName),
"--namespace", namespace,
"--values", valuesFile,
"--wait",
"--timeout", "5m",
}
if err := h.runHelm(args...); err != nil {
return fmt.Errorf("helm install %s: %w", chartName, err)
}
log.Printf("Chart %s installé dans %s", chartName, namespace)
return nil
}
// upgradeInstallChart installe ou met à jour un chart de façon idempotente
// (helm upgrade --install) — pour l'infra partagée (Traefik), appelée à
// chaque démarrage de l'API et qui ne doit pas échouer si déjà en place.
func (h *HelmProvisioner) upgradeInstallChart(namespace, release, chartName string, values map[string]interface{}) error {
args := []string{
"upgrade", release,
filepath.Join(h.chartsDir, chartName),
"--install",
"--namespace", namespace,
"--wait",
"--timeout", "5m",
}
if values != nil {
valuesFile, cleanup, err := h.writeValuesFile(namespace, chartName, values)
if err != nil {
return err
}
defer cleanup()
args = append(args, "--values", valuesFile)
}
if err := h.runHelm(args...); err != nil {
return fmt.Errorf("helm upgrade --install %s: %w", chartName, err)
}
log.Printf("Chart %s (release %s) à jour dans %s", chartName, release, namespace)
return nil
}
// runHelmDependencyUpdate télécharge les subcharts déclarés dans
// Chart.yaml (dependencies) — requis avant d'installer un chart qui, comme
// traefik, dépend d'un chart tiers.
func (h *HelmProvisioner) runHelmDependencyUpdate(chartPath string) error {
return h.runHelm("dependency", "update", chartPath)
}
// writeValuesFile sérialise des valeurs Helm dans un fichier temporaire et
// retourne son chemin + une fonction de nettoyage à appeler en defer.
func (h *HelmProvisioner) writeValuesFile(namespace, chartName string, values map[string]interface{}) (string, func(), error) {
valuesFile := filepath.Join("/tmp", fmt.Sprintf("values-%s-%s.yaml", namespace, chartName))
valuesYAML, err := yaml.Marshal(values)
if err != nil {
return "", nil, fmt.Errorf("marshal YAML: %w", err)
}
if err := os.WriteFile(valuesFile, valuesYAML, 0644); err != nil {
return "", nil, fmt.Errorf("write values file: %w", err)
}
return valuesFile, func() { os.Remove(valuesFile) }, nil
}
// runHelm exécute une commande helm et remonte stdout/stderr en cas d'erreur.
func (h *HelmProvisioner) runHelm(args ...string) error {
cmd := exec.Command(h.helmPath, args...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("helm install %s: %v (stdout: %s, stderr: %s)", chartName, err, stdout.String(), stderr.String())
return fmt.Errorf("%v (stdout: %s, stderr: %s)", err, stdout.String(), stderr.String())
}
log.Printf("Chart %s installé dans %s", chartName, namespace)
return nil
}
@@ -244,7 +340,9 @@ func (h *HelmProvisioner) buildBackendValues(d Demo, resources []ExternalResourc
// buildFrontendValues construit les valeurs pour le chart frontend.
func (h *HelmProvisioner) buildFrontendValues(d Demo) map[string]interface{} {
backendURL := fmt.Sprintf("http://%s-backend.%s.svc.cluster.local:8080", d.Namespace, d.Namespace)
// Nom réel du Service backend : "<namespace>-backend" (release helm) +
// "-gestion-backend" (nom du chart, ajouté par le helper fullname).
backendURL := fmt.Sprintf("http://%s-backend-gestion-backend.%s.svc.cluster.local:8080", d.Namespace, d.Namespace)
// Parser l'image frontend pour séparer repository et tag
frontendRepo, frontendTag := parseImage(h.frontendImage)
@@ -266,6 +364,15 @@ func (h *HelmProvisioner) buildFrontendValues(d Demo) map[string]interface{} {
}
}
// buildIngressRouteValues construit les valeurs pour le chart ingressroute,
// qui raccorde cette démo au Traefik partagé (WAF + routage HTTPS).
func (h *HelmProvisioner) buildIngressRouteValues(d Demo) map[string]interface{} {
return map[string]interface{}{
"host": fmt.Sprintf("%s.%s", d.Namespace, h.baseDomain),
"traefikNamespace": sharedTraefikNamespace,
}
}
// buildSecrets extrait les références des secrets des ressources externes.
func (h *HelmProvisioner) buildSecrets(resources []ExternalResource) map[string]string {
secrets := map[string]string{}