// Изменено: 2026-03-11 // Handler — общий контейнер зависимостей для всех REST handlers. // Все handlers получают доступ к k8s, S3 и Postgres через эту структуру. // Логирование через slog, маршрутизация через gorilla/mux. // // Архитектура namespace: // - Каждый пользователь работает в своём k8s namespace. // - Имя namespace вычисляется провайдером из JWT-токена (SHA256 от sub). // - Оператор сам не создаёт namespace заранее — он делает это при первом // Create-запросе через ensureNamespace(). // - defaultNamespace используется только как fallback для dev/тестов, // когда namespace не передан в URL. package handler import ( "context" "encoding/json" "log/slog" "net/http" "github.com/gorilla/mux" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" "gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/internal/storage/postgres" "gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/internal/storage/s3" ) // defaultNamespace — fallback namespace для dev/тестов. // В production namespace определяется из JWT-токена провайдером. // Все обращения к "default" строке идут через эту константу — одно место замены. const defaultNamespace = "default" // Handler содержит зависимости для всех REST-обработчиков. type Handler struct { K8s client.Client Scheme *runtime.Scheme S3 *s3.Client PG *postgres.Store Log *slog.Logger } // writeJSON отправляет JSON-ответ с указанным статусом. func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) } // errResp возвращает структуру ошибки для JSON. func errResp(msg string) map[string]string { return map[string]string{"error": msg} } // pathVar читает переменную пути из gorilla/mux. func pathVar(r *http.Request, key string) string { return mux.Vars(r)[key] } // namespace читает {namespace} из пути URL. // Fallback — defaultNamespace (используется только в dev/тестах). func namespace(r *http.Request) string { if ns := mux.Vars(r)["namespace"]; ns != "" { return ns } return defaultNamespace } // ensureNamespace создаёт k8s namespace если он не существует. // Вызывается в начале каждого Create-хендлера (CreateFunction, CreateTrigger, CreateJob). // Идемпотентен: если namespace уже есть — не ошибается. // Зачем здесь, а не в контроллере: оператор получает запросы ДО того как // контроллер может что-то создать, поэтому namespace нужен прямо при Create. func (h *Handler) ensureNamespace(ctx context.Context, ns string) error { existing := &corev1.Namespace{} err := h.K8s.Get(ctx, client.ObjectKey{Name: ns}, existing) if err == nil { // namespace уже существует — всё ок return nil } if !k8serrors.IsNotFound(err) { // неожиданная ошибка k8s API return err } // namespace не существует — создаём ns_obj := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: ns, // label для идентификации namespace как принадлежащего sless Labels: map[string]string{ "managed-by": "sless-operator", }, }, } return h.K8s.Create(ctx, ns_obj) }