@@ -102,11 +102,12 @@ func (h *HelmProvisioner) Provision(d Demo, resources []ExternalResource, cfg Pr
|
|||||||
return fmt.Errorf("création namespace %s: %w", d.Namespace, err)
|
return fmt.Errorf("création namespace %s: %w", d.Namespace, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Préparer les valeurs. Pas de persistence à la création : les démos
|
// PVC dès la création de la démo (pas d'emptyDir) : si le client ne
|
||||||
// d'essai sont éphémères (TTL 30j) — voir UpgradeToPaid pour le passage
|
// souscrit pas, le teardown du namespace (TTL ou suppression admin)
|
||||||
// en stockage persistant lorsqu'un client devient payant.
|
// supprime le PVC avec le reste. S'il souscrit, TransferToPaid n'a rien
|
||||||
postgresValues := h.buildPostgresValues(d, false)
|
// d'autre à faire côté stockage — les données sont déjà en place.
|
||||||
redisValues := h.buildRedisValues(d, false)
|
postgresValues := h.buildPostgresValues(d)
|
||||||
|
redisValues := h.buildRedisValues(d)
|
||||||
|
|
||||||
// Secret partagé backend <-> lbtelegram (authentifie les appels de
|
// Secret partagé backend <-> lbtelegram (authentifie les appels de
|
||||||
// lbtelegram vers backend) — généré une seule fois si le load-balancer
|
// lbtelegram vers backend) — généré une seule fois si le load-balancer
|
||||||
@@ -199,7 +200,8 @@ func (h *HelmProvisioner) EnsureSharedInfra() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Teardown détruit une démo en supprimant son namespace (cascading).
|
// Teardown détruit une démo en supprimant son namespace (cascading) — les
|
||||||
|
// PVC postgres/redis de la démo partent avec, puisqu'ils sont namespaced.
|
||||||
func (h *HelmProvisioner) Teardown(d Demo) error {
|
func (h *HelmProvisioner) Teardown(d Demo) error {
|
||||||
return h.deleteNamespace(d.Namespace)
|
return h.deleteNamespace(d.Namespace)
|
||||||
}
|
}
|
||||||
@@ -259,56 +261,6 @@ const (
|
|||||||
demoDBPass = "demo-postgres-pass"
|
demoDBPass = "demo-postgres-pass"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UpgradeToPaid bascule postgresql/redis d'une démo en stockage persistant
|
|
||||||
// lorsqu'un client passe en abonnement payant, en préservant les données
|
|
||||||
// postgres déjà créées pendant l'essai : pg_dump avant la bascule (le volume
|
|
||||||
// actuel est un emptyDir éphémère), puis restore une fois le nouveau volume
|
|
||||||
// persistant en place. Redis n'est pas migré (cache/sessions — reconstruit
|
|
||||||
// naturellement), seule sa persistence est activée pour la suite.
|
|
||||||
func (h *HelmProvisioner) UpgradeToPaid(d Demo) error {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
pgPod, err := h.findPod(ctx, d.Namespace, "postgresql")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("pod postgresql introuvable: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dumpCmd := fmt.Sprintf("PGPASSWORD=%s pg_dump -h localhost -U %s %s", demoDBPass, demoDBUser, demoDBName)
|
|
||||||
dump, stderr, err := h.execInPod(ctx, d.Namespace, pgPod, "postgresql", []string{"sh", "-c", dumpCmd}, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("pg_dump: %w (%s)", err, stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bascule postgresql en stockage persistant : le pod redémarre (nouveau
|
|
||||||
// PVC, initialement vide) — c'est pour ça que le dump ci-dessus doit
|
|
||||||
// être pris AVANT cet appel. helm upgrade --wait attend que le nouveau
|
|
||||||
// pod soit prêt (readinessProbe = pg_isready) avant de retourner.
|
|
||||||
if err := h.upgradeInstallChart(d.Namespace, d.Namespace+"-postgresql", "postgresql", h.buildPostgresValues(d, true)); err != nil {
|
|
||||||
return fmt.Errorf("upgrade postgresql (persistence): %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
newPgPod, err := h.findPod(ctx, d.Namespace, "postgresql")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("pod postgresql introuvable après upgrade: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.TrimSpace(dump) != "" {
|
|
||||||
restoreCmd := fmt.Sprintf("PGPASSWORD=%s psql -h localhost -U %s %s", demoDBPass, demoDBUser, demoDBName)
|
|
||||||
_, stderr, err := h.execInPod(ctx, d.Namespace, newPgPod, "postgresql", []string{"sh", "-c", restoreCmd}, strings.NewReader(dump))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("restore pg_dump: %w (%s)", err, stderr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.upgradeInstallChart(d.Namespace, d.Namespace+"-redis", "redis", h.buildRedisValues(d, true)); err != nil {
|
|
||||||
return fmt.Errorf("upgrade redis (persistence): %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Démo %s basculée en stockage persistant (données postgres migrées)", d.Namespace)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// findPod retourne le nom du premier pod du namespace dont le nom contient nameContains.
|
// findPod retourne le nom du premier pod du namespace dont le nom contient nameContains.
|
||||||
func (h *HelmProvisioner) findPod(ctx context.Context, namespace, nameContains string) (string, error) {
|
func (h *HelmProvisioner) findPod(ctx context.Context, namespace, nameContains string) (string, error) {
|
||||||
pods, err := h.k8sClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
|
pods, err := h.k8sClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
|
||||||
@@ -694,7 +646,8 @@ func (h *HelmProvisioner) buildSecrets(resources []ExternalResource) map[string]
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildPostgresValues construit les valeurs pour le chart postgresql.
|
// buildPostgresValues construit les valeurs pour le chart postgresql.
|
||||||
func (h *HelmProvisioner) buildPostgresValues(d Demo, persistent bool) map[string]interface{} {
|
// Toujours persistant (PVC) : voir Provision.
|
||||||
|
func (h *HelmProvisioner) buildPostgresValues(d Demo) map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"auth": map[string]interface{}{
|
"auth": map[string]interface{}{
|
||||||
"password": "demo-postgres-pass", // Mot de passe OBLIGATOIRE (champ correct pour le chart)
|
"password": "demo-postgres-pass", // Mot de passe OBLIGATOIRE (champ correct pour le chart)
|
||||||
@@ -706,13 +659,14 @@ func (h *HelmProvisioner) buildPostgresValues(d Demo, persistent bool) map[strin
|
|||||||
"port": 5432,
|
"port": 5432,
|
||||||
},
|
},
|
||||||
"persistence": map[string]interface{}{
|
"persistence": map[string]interface{}{
|
||||||
"enabled": persistent,
|
"enabled": true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildRedisValues construit les valeurs pour le chart redis.
|
// buildRedisValues construit les valeurs pour le chart redis.
|
||||||
func (h *HelmProvisioner) buildRedisValues(d Demo, persistent bool) map[string]interface{} {
|
// Toujours persistant (PVC) : voir Provision.
|
||||||
|
func (h *HelmProvisioner) buildRedisValues(d Demo) map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"service": map[string]interface{}{
|
"service": map[string]interface{}{
|
||||||
"type": "ClusterIP",
|
"type": "ClusterIP",
|
||||||
@@ -722,7 +676,7 @@ func (h *HelmProvisioner) buildRedisValues(d Demo, persistent bool) map[string]i
|
|||||||
"password": "demo-redis-pass", // Mot de passe simple pour les démos
|
"password": "demo-redis-pass", // Mot de passe simple pour les démos
|
||||||
},
|
},
|
||||||
"persistence": map[string]interface{}{
|
"persistence": map[string]interface{}{
|
||||||
"enabled": persistent,
|
"enabled": true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,6 @@ type Provisioner interface {
|
|||||||
Provision(d Demo, resources []ExternalResource, cfg ProvisionConfig) error
|
Provision(d Demo, resources []ExternalResource, cfg ProvisionConfig) error
|
||||||
// Teardown demande la destruction d'une démo.
|
// Teardown demande la destruction d'une démo.
|
||||||
Teardown(d Demo) error
|
Teardown(d Demo) error
|
||||||
// UpgradeToPaid bascule postgresql/redis en stockage persistant, en
|
|
||||||
// migrant les données postgres déjà créées (pg_dump/restore).
|
|
||||||
UpgradeToPaid(d Demo) error
|
|
||||||
GetResourceState(ctx context.Context, namespace string) (ResourceState, error)
|
GetResourceState(ctx context.Context, namespace string) (ResourceState, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +19,6 @@ type NoopProvisioner struct{}
|
|||||||
|
|
||||||
func (NoopProvisioner) Provision(Demo, []ExternalResource, ProvisionConfig) error { return nil }
|
func (NoopProvisioner) Provision(Demo, []ExternalResource, ProvisionConfig) error { return nil }
|
||||||
func (NoopProvisioner) Teardown(Demo) error { return nil }
|
func (NoopProvisioner) Teardown(Demo) error { return nil }
|
||||||
func (NoopProvisioner) UpgradeToPaid(Demo) error { return nil }
|
|
||||||
func (NoopProvisioner) GetResourceState(ctx context.Context, namespace string) (ResourceState, error) {
|
func (NoopProvisioner) GetResourceState(ctx context.Context, namespace string) (ResourceState, error) {
|
||||||
return ResourceState{}, nil
|
return ResourceState{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,11 +135,11 @@ func (s *Service) ListForUser(username string) ([]Demo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TransferToPaid marque la démo active du client comme abonnement payant
|
// TransferToPaid marque la démo active du client comme abonnement payant
|
||||||
// (n'expire plus) et déclenche la bascule en stockage persistant (migration
|
// (n'expire plus). Le stockage (PVC postgres/redis) est déjà en place depuis
|
||||||
// des données postgres incluse, voir Provisioner.UpgradeToPaid). No-op si le
|
// la création de la démo (voir HelmProvisioner.Provision) : rien à basculer
|
||||||
// client n'a pas de démo active — ce n'est pas une erreur (ex: compte créé
|
// côté infra, seul le statut change. No-op si le client n'a pas de démo
|
||||||
// sans jamais avoir demandé de démo). L'upgrade infra est lent (dump/
|
// active — ce n'est pas une erreur (ex: compte créé sans jamais avoir
|
||||||
// restore/helm) : il tourne en tâche de fond, cette méthode ne bloque pas.
|
// demandé de démo).
|
||||||
func (s *Service) TransferToPaid(username string) error {
|
func (s *Service) TransferToPaid(username string) error {
|
||||||
existing, err := s.store.ListByUsername(auth.NormalizeUsername(username))
|
existing, err := s.store.ListByUsername(auth.NormalizeUsername(username))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -159,17 +159,10 @@ func (s *Service) TransferToPaid(username string) error {
|
|||||||
|
|
||||||
active.TypeAbo = "premium"
|
active.TypeAbo = "premium"
|
||||||
active.ExpiresAt = s.now().UTC().AddDate(10, 0, 0) // n'expire plus, en pratique
|
active.ExpiresAt = s.now().UTC().AddDate(10, 0, 0) // n'expire plus, en pratique
|
||||||
updated, err := s.store.Update(*active)
|
if _, err := s.store.Update(*active); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
|
||||||
if err := s.prov.UpgradeToPaid(updated); err != nil {
|
|
||||||
log.Printf("Transfert payant échoué pour %s: %v", updated.Namespace, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user