From 1676bf3c5a56cef5bbb214d481ffc470def41704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Mon, 9 Mar 2026 19:23:28 +0400 Subject: [PATCH] =?UTF-8?q?fix:=20destroy=20route=20cleanup=20bug=20?= =?UTF-8?q?=E2=80=94=20delete=20Service+Ingress=20on=20trigger/function=20?= =?UTF-8?q?deletion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - controllers/trigger_controller.go: handleTriggerDeletion теперь удаляет Service и Ingress из sless-fn-{ns} при удалении HTTP-триггера - controllers/function_controller.go: handleDeletion теперь удаляет Service и Ingress (имена совпадают с fn.Name); добавлен импорт netv1 - terraform/provider trigger_resource.go: Delete ждёт пока GetTrigger вернёт 404 (до 90с) — провайдер не возвращает успех раньше чем cleanup завершён - examples: версия провайдера обновлена до ~> 0.1.11 Operator: naeel/sless-operator:v0.1.17 Provider: v0.1.11 Fixes DESTROY_ROUTE_CLEANUP_BUG.md --- controllers/function_controller.go | 14 ++++++++++++- controllers/trigger_controller.go | 21 +++++++++++++++++-- examples/hello-node/main.tf | 2 +- examples/notes-python/main.tf | 2 +- examples/simple-node/main.tf | 2 +- examples/simple-python/main.tf | 2 +- .../internal/resources/trigger_resource.go | 21 ++++++++++++++++++- 7 files changed, 56 insertions(+), 8 deletions(-) diff --git a/controllers/function_controller.go b/controllers/function_controller.go index 24eec1d..4b4517e 100644 --- a/controllers/function_controller.go +++ b/controllers/function_controller.go @@ -13,6 +13,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + netv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -302,7 +303,7 @@ func (r *FunctionReconciler) ensureRegistrySecret(ctx context.Context, targetNS return nil } -// handleDeletion обрабатывает удаление Function: удаляет Deployment и убирает finalizer. +// handleDeletion обрабатывает удаление Function: удаляет Deployment, Service, Ingress и убирает finalizer. func (r *FunctionReconciler) handleDeletion(ctx context.Context, fn *slessv1alpha1.Function) (ctrl.Result, error) { deployNS := "sless-fn-" + fn.Namespace dep := &appsv1.Deployment{} @@ -310,6 +311,17 @@ func (r *FunctionReconciler) handleDeletion(ctx context.Context, fn *slessv1alph _ = r.Delete(ctx, dep) } + // Удаляем Service и Ingress — созданы HTTP триггером, но именованы по функции. + // Если function_controller не удалит их, Ingress остаётся после destroy → 502. + svc := &corev1.Service{} + if err := r.Get(ctx, client.ObjectKey{Name: fn.Name, Namespace: deployNS}, svc); err == nil { + _ = r.Delete(ctx, svc) + } + ing := &netv1.Ingress{} + if err := r.Get(ctx, client.ObjectKey{Name: fn.Name, Namespace: deployNS}, ing); err == nil { + _ = r.Delete(ctx, ing) + } + fn.Finalizers = removeString(fn.Finalizers, finalizerName) if err := r.Update(ctx, fn); err != nil { return ctrl.Result{}, fmt.Errorf("remove finalizer: %w", err) diff --git a/controllers/trigger_controller.go b/controllers/trigger_controller.go index 8264ec9..a50207b 100644 --- a/controllers/trigger_controller.go +++ b/controllers/trigger_controller.go @@ -282,8 +282,25 @@ func (r *TriggerReconciler) handleTriggerDeletion(ctx context.Context, tr *sless _ = r.Delete(ctx, cj) } } - // Ingress/Service для HTTP триггеров оставляем — они могут быть нужны другим триггерам, - // полная очистка происходит при удалении Function через function_controller finalizer + + // Для HTTP триггеров удаляем Service и Ingress из sless-fn-{namespace}. + // Имена ресурсов совпадают с именем функции (FunctionRef). + // Без этого Ingress остаётся висеть после destroy и endpoint возвращает 502. + if tr.Spec.Type == slessv1alpha1.TriggerTypeHTTP { + deployNS := "sless-fn-" + tr.Namespace + fnName := tr.Spec.FunctionRef + + svc := &corev1.Service{} + if err := r.Get(ctx, client.ObjectKey{Name: fnName, Namespace: deployNS}, svc); err == nil { + _ = r.Delete(ctx, svc) + } + + ing := &netv1.Ingress{} + if err := r.Get(ctx, client.ObjectKey{Name: fnName, Namespace: deployNS}, ing); err == nil { + _ = r.Delete(ctx, ing) + } + } + tr.Finalizers = removeString(tr.Finalizers, triggerFinalizer) if err := r.Update(ctx, tr); err != nil { return ctrl.Result{}, fmt.Errorf("remove trigger finalizer: %w", err) diff --git a/examples/hello-node/main.tf b/examples/hello-node/main.tf index cfb00b4..15b7bb1 100644 --- a/examples/hello-node/main.tf +++ b/examples/hello-node/main.tf @@ -8,7 +8,7 @@ terraform { required_providers { sless = { source = "terra.k8c.ru/naeel/sless" - version = "~> 0.1.10" + version = "~> 0.1.11" } } } diff --git a/examples/notes-python/main.tf b/examples/notes-python/main.tf index 505e104..f8b28a2 100644 --- a/examples/notes-python/main.tf +++ b/examples/notes-python/main.tf @@ -14,7 +14,7 @@ terraform { # Провайдер для управления serverless функциями через sless API sless = { source = "terra.k8c.ru/naeel/sless" - version = "~> 0.1.10" + version = "~> 0.1.11" } } } diff --git a/examples/simple-node/main.tf b/examples/simple-node/main.tf index 9cb2843..a4cda22 100644 --- a/examples/simple-node/main.tf +++ b/examples/simple-node/main.tf @@ -19,7 +19,7 @@ terraform { required_providers { sless = { source = "terra.k8c.ru/naeel/sless" - version = "~> 0.1.10" + version = "~> 0.1.11" } } } diff --git a/examples/simple-python/main.tf b/examples/simple-python/main.tf index 5032776..75a1e6a 100644 --- a/examples/simple-python/main.tf +++ b/examples/simple-python/main.tf @@ -18,7 +18,7 @@ terraform { required_providers { sless = { source = "terra.k8c.ru/naeel/sless" - version = "~> 0.1.10" + version = "~> 0.1.11" } } } diff --git a/terraform/provider/internal/resources/trigger_resource.go b/terraform/provider/internal/resources/trigger_resource.go index 3133119..b3db9c9 100644 --- a/terraform/provider/internal/resources/trigger_resource.go +++ b/terraform/provider/internal/resources/trigger_resource.go @@ -209,9 +209,28 @@ func (r *TriggerResource) Delete(ctx context.Context, req resource.DeleteRequest return } - if err := r.client.DeleteTrigger(ctx, r.client.Namespace, state.Name.ValueString()); err != nil { + name := state.Name.ValueString() + if err := r.client.DeleteTrigger(ctx, r.client.Namespace, name); err != nil { resp.Diagnostics.AddError("delete trigger", err.Error()) + return } + + // Ждём, пока триггер исчезнет из API. + // Финалайзер контроллера снимается ПОСЛЕ того, как Service+Ingress удалены, + // поэтому когда GetTrigger возвращает nil (404) — маршрут уже убран и endpoint мёртв. + deadline := time.Now().Add(90 * time.Second) + for time.Now().Before(deadline) { + tr, err := r.client.GetTrigger(ctx, r.client.Namespace, name) + if err != nil { + resp.Diagnostics.AddError("wait trigger gone", err.Error()) + return + } + if tr == nil { + return // триггер исчез — cleanup завершён + } + time.Sleep(3 * time.Second) + } + resp.Diagnostics.AddError("wait trigger gone", fmt.Sprintf("trigger %s still exists after 90s", name)) } // trToModel конвертирует API-ответ → state модель.