chore: build
This commit is contained in:
@@ -55,6 +55,7 @@ type HelmProvisioner struct {
|
||||
|
||||
// NewHelmProvisioner crée un nouveau provisioner Helm.
|
||||
// chartsDir : chemin absolu vers le dossier contenant les charts (backend/, frontend/)
|
||||
// vitrineChartsDir : idem pour les charts des projets vitrine (voir internal/projects)
|
||||
// helmPath : chemin vers l'exécutable helm (optionnel, default: "helm")
|
||||
// metricsClient : client metrics.k8s.io pour l'usage CPU/mémoire live (optionnel, peut être nil).
|
||||
func NewHelmProvisioner(
|
||||
@@ -63,6 +64,7 @@ func NewHelmProvisioner(
|
||||
restConfig *rest.Config,
|
||||
metricsClient *metricsclient.Clientset,
|
||||
chartsDir string,
|
||||
vitrineChartsDir string,
|
||||
helmPath string,
|
||||
) (*HelmProvisioner, error) {
|
||||
if chartsDir == "" {
|
||||
@@ -89,7 +91,7 @@ func NewHelmProvisioner(
|
||||
metricsClient: metricsClient,
|
||||
dynamicClient: dynamicClient,
|
||||
chartsDir: chartsDir,
|
||||
vitrineChartsDir: cfg.VitrineChartsDir,
|
||||
vitrineChartsDir: vitrineChartsDir,
|
||||
frontendImage: cfg.FrontendImage,
|
||||
backendImage: cfg.BackendImage,
|
||||
lbtelegramImage: cfg.LBTelegramImage,
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
package demos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Projets vitrine (voir internal/projects) : même infrastructure que les
|
||||
// démos (namespace isolé, NetworkPolicy, ResourceQuota, Traefik + WAF
|
||||
// partagés, sauvegardes Velero par namespace) mais avec les charts du
|
||||
// produit vitrine (h.vitrineChartsDir) et une durée d'abonnement en mois.
|
||||
// Le cycle de vie (durée, suspension, suppression) vit dans internal/projects ;
|
||||
// Le cycle de vie (durée, prolongation, suppression) vit dans internal/projects ;
|
||||
// ce fichier ne contient que ce qui touche au cluster.
|
||||
|
||||
// Noms de release/chart utilisés par les charts vitrine (dossier
|
||||
@@ -29,10 +26,6 @@ func vitrineFrontendService(namespace string) string {
|
||||
return namespace + "-frontend-vitrine-frontend"
|
||||
}
|
||||
|
||||
// replicasBeforeSuspendAnnotation : nombre de réplicas d'une charge de travail
|
||||
// avant sa suspension, pour la relancer à l'identique (voir SuspendProject).
|
||||
const replicasBeforeSuspendAnnotation = "omnex.app/replicas-before-suspend"
|
||||
|
||||
// ProjectSpec : ce dont le provisioner a besoin pour déployer un projet
|
||||
// vitrine. AdminPassword ne doit jamais être persisté : il ne transite que
|
||||
// par le fichier de valeurs Helm temporaire puis le Secret k8s du chart.
|
||||
@@ -128,92 +121,6 @@ func (h *HelmProvisioner) TeardownProject(namespace string) error {
|
||||
return h.Teardown(Demo{Namespace: namespace})
|
||||
}
|
||||
|
||||
// SuspendProject met à 0 les Deployment et StatefulSet du namespace : le
|
||||
// projet n'est plus servi (Traefik répond 503) mais ses PVC, ses Secrets et
|
||||
// son IngressRoute restent en place. Le nombre de réplicas d'origine est
|
||||
// noté en annotation pour ResumeProject. Idempotent.
|
||||
func (h *HelmProvisioner) SuspendProject(namespace string) error {
|
||||
return h.scaleProject(namespace, false)
|
||||
}
|
||||
|
||||
// ResumeProject relance ce que SuspendProject a arrêté, au nombre de
|
||||
// réplicas d'origine (1 à défaut d'annotation). Idempotent.
|
||||
func (h *HelmProvisioner) ResumeProject(namespace string) error {
|
||||
return h.scaleProject(namespace, true)
|
||||
}
|
||||
|
||||
func (h *HelmProvisioner) scaleProject(namespace string, up bool) error {
|
||||
ctx := context.Background()
|
||||
|
||||
deploys, err := h.k8sClient.AppsV1().Deployments(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("liste des deployments de %s: %w", namespace, err)
|
||||
}
|
||||
for i := range deploys.Items {
|
||||
d := &deploys.Items[i]
|
||||
if d.Spec.Replicas == nil {
|
||||
one := int32(1)
|
||||
d.Spec.Replicas = &one
|
||||
}
|
||||
target, changed := nextReplicas(d.Spec.Replicas, &d.Annotations, up)
|
||||
if !changed {
|
||||
continue
|
||||
}
|
||||
d.Spec.Replicas = &target
|
||||
if _, err := h.k8sClient.AppsV1().Deployments(namespace).Update(ctx, d, metav1.UpdateOptions{}); err != nil {
|
||||
return fmt.Errorf("mise à l'échelle du deployment %s: %w", d.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
sets, err := h.k8sClient.AppsV1().StatefulSets(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("liste des statefulsets de %s: %w", namespace, err)
|
||||
}
|
||||
for i := range sets.Items {
|
||||
s := &sets.Items[i]
|
||||
if s.Spec.Replicas == nil {
|
||||
one := int32(1)
|
||||
s.Spec.Replicas = &one
|
||||
}
|
||||
target, changed := nextReplicas(s.Spec.Replicas, &s.Annotations, up)
|
||||
if !changed {
|
||||
continue
|
||||
}
|
||||
s.Spec.Replicas = &target
|
||||
if _, err := h.k8sClient.AppsV1().StatefulSets(namespace).Update(ctx, s, metav1.UpdateOptions{}); err != nil {
|
||||
return fmt.Errorf("mise à l'échelle du statefulset %s: %w", s.Name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextReplicas calcule le nombre de réplicas cible d'une charge de travail et
|
||||
// met à jour ses annotations. changed=false : rien à faire (déjà dans l'état
|
||||
// demandé).
|
||||
func nextReplicas(current *int32, annotations *map[string]string, up bool) (target int32, changed bool) {
|
||||
if !up {
|
||||
if *current == 0 {
|
||||
return 0, false
|
||||
}
|
||||
if *annotations == nil {
|
||||
*annotations = map[string]string{}
|
||||
}
|
||||
(*annotations)[replicasBeforeSuspendAnnotation] = strconv.Itoa(int(*current))
|
||||
return 0, true
|
||||
}
|
||||
if *current != 0 {
|
||||
return *current, false
|
||||
}
|
||||
target = 1
|
||||
if v, ok := (*annotations)[replicasBeforeSuspendAnnotation]; ok {
|
||||
if n, err := strconv.Atoi(v); err == nil && n > 0 {
|
||||
target = int32(n)
|
||||
}
|
||||
delete(*annotations, replicasBeforeSuspendAnnotation)
|
||||
}
|
||||
return target, true
|
||||
}
|
||||
|
||||
func (h *HelmProvisioner) buildVitrinePostgresValues(password string) map[string]interface{} {
|
||||
// Nom de base et utilisateur : défauts du chart (vitrine_db / postgres),
|
||||
// alignés sur database.name / database.user du chart backend.
|
||||
|
||||
@@ -14,55 +14,6 @@ import (
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
func TestNextReplicas_SuspendThenResumeRestoresOriginalCount(t *testing.T) {
|
||||
annotations := map[string]string{}
|
||||
current := ptr(int32(3))
|
||||
|
||||
target, changed := nextReplicas(current, &annotations, false)
|
||||
if !changed || target != 0 {
|
||||
t.Fatalf("suspension: target=%d changed=%v", target, changed)
|
||||
}
|
||||
if annotations[replicasBeforeSuspendAnnotation] != "3" {
|
||||
t.Fatalf("annotation=%q, attendu 3", annotations[replicasBeforeSuspendAnnotation])
|
||||
}
|
||||
|
||||
// Déjà à 0 : suspendre à nouveau ne doit pas écraser l'annotation.
|
||||
current = ptr(int32(0))
|
||||
if _, changed := nextReplicas(current, &annotations, false); changed {
|
||||
t.Error("suspension répétée non idempotente")
|
||||
}
|
||||
if annotations[replicasBeforeSuspendAnnotation] != "3" {
|
||||
t.Error("l'annotation a été écrasée")
|
||||
}
|
||||
|
||||
target, changed = nextReplicas(current, &annotations, true)
|
||||
if !changed || target != 3 {
|
||||
t.Fatalf("relance: target=%d changed=%v, attendu 3", target, changed)
|
||||
}
|
||||
if _, still := annotations[replicasBeforeSuspendAnnotation]; still {
|
||||
t.Error("annotation non nettoyée après la relance")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextReplicas_ResumeWithoutAnnotationDefaultsToOne(t *testing.T) {
|
||||
annotations := map[string]string{}
|
||||
target, changed := nextReplicas(ptr(int32(0)), &annotations, true)
|
||||
if !changed || target != 1 {
|
||||
t.Errorf("target=%d changed=%v, attendu 1", target, changed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextReplicas_ResumeRunningWorkloadIsNoop(t *testing.T) {
|
||||
annotations := map[string]string{}
|
||||
if _, changed := nextReplicas(ptr(int32(2)), &annotations, true); changed {
|
||||
t.Error("relance d'une charge déjà active")
|
||||
}
|
||||
}
|
||||
|
||||
// TestVitrineValuesRenderWithCharts génère les valeurs comme ProvisionProject
|
||||
// puis rend les charts de deploy/chart-vitrine avec helm : garantit que les
|
||||
// clés attendues existent, que les validations des charts passent et que les
|
||||
// noms de Services calculés côté Go sont bien ceux des charts.
|
||||
func TestVitrineValuesRenderWithCharts(t *testing.T) {
|
||||
if _, err := exec.LookPath("helm"); err != nil {
|
||||
t.Skip("helm absent du PATH")
|
||||
|
||||
Reference in New Issue
Block a user