@@ -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{}
|
||||
|
||||
Reference in New Issue
Block a user