// Изменено: 2026-03-11 // namespace.go — хендлер управления namespace пользователя. // Ответственность: создание k8s namespace (один раз, при инициализации провайдера). // Вынесен из handler.go намеренно: handler.go — базовый файл без бизнес-логики, // а работа с corev1/metav1/k8serrors — это бизнес-логика namespace lifecycle. // // Точка вызова: провайдер terraform после Configure() вызывает // POST /v1/namespaces/{namespace}/ensure ОДИН РАЗ перед созданием любых ресурсов. // Resource-хендлеры (functions, triggers, jobs) namespace НЕ трогают. package handler import ( "net/http" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) // EnsureNamespace — хендлер POST /v1/namespaces/{namespace}/ensure. // Создаёт k8s namespace если не существует. Идемпотентен: если namespace уже есть — // возвращает 200 OK вместо 201 Created. // Вызывается провайдером ОДИН РАЗ в Configure() — до создания любых ресурсов. func (h *Handler) EnsureNamespace(w http.ResponseWriter, r *http.Request) { ns := namespace(r) existing := &corev1.Namespace{} err := h.K8s.Get(r.Context(), client.ObjectKey{Name: ns}, existing) if err == nil { // namespace уже существует writeJSON(w, http.StatusOK, map[string]string{"namespace": ns, "status": "exists"}) return } if !k8serrors.IsNotFound(err) { writeJSON(w, http.StatusInternalServerError, errResp(err.Error())) return } nsObj := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: ns, Labels: map[string]string{ "managed-by": "sless-operator", }, }, } if err := h.K8s.Create(r.Context(), nsObj); err != nil { // параллельный запрос уже создал namespace — идемпотентность if k8serrors.IsAlreadyExists(err) { writeJSON(w, http.StatusOK, map[string]string{"namespace": ns, "status": "exists"}) return } writeJSON(w, http.StatusInternalServerError, errResp(err.Error())) return } writeJSON(w, http.StatusCreated, map[string]string{"namespace": ns, "status": "created"}) }