feat: REST API (gorilla/mux + slog) + trigger controller + main.go wiring

- controllers/trigger_controller.go: полная реализация, HTTP->Ingress+Service, Cron->CronJob
- internal/config: добавлены IngressHost, APIToken
- internal/api/router.go: gorilla/mux роутер /v1/namespaces/{ns}/...
- internal/api/handler/: functions, triggers, invocations, base handler с slog
- internal/api/middleware/: auth (Bearer token) + logging (slog)
- main.go: запуск operator + HTTP сервера параллельно
- go.sum: добавлен gorilla/mux v1.8.1
This commit is contained in:
“Naeel”
2026-03-07 08:46:10 +04:00
parent 5f25f61948
commit 43e7d0ea48
21 changed files with 1720 additions and 112 deletions
+40
View File
@@ -0,0 +1,40 @@
// Изменено: 2026-03-07
// Auth middleware — проверяет Bearer токен из заголовка Authorization.
// В v1: сравниваем с SLESS_API_TOKEN из конфига.
// В prod: вызываем auth-сервис nubes.ru (TODO v2).
package middleware
import (
"log/slog"
"net/http"
"strings"
)
// Auth возвращает middleware которое требует заголовок:
//
// Authorization: Bearer <token>
//
// и сравнивает его с allowedToken.
func Auth(allowedToken string, log *slog.Logger, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
header := r.Header.Get("Authorization")
if header == "" {
log.Warn("auth: missing authorization header", "remote", r.RemoteAddr, "path", r.URL.Path)
http.Error(w, `{"error":"authorization required"}`, http.StatusUnauthorized)
return
}
parts := strings.SplitN(header, " ", 2)
if len(parts) != 2 || !strings.EqualFold(parts[0], "bearer") {
log.Warn("auth: invalid authorization format", "remote", r.RemoteAddr)
http.Error(w, `{"error":"invalid authorization format, use Bearer <token>"}`, http.StatusUnauthorized)
return
}
if parts[1] != allowedToken {
log.Warn("auth: invalid token", "remote", r.RemoteAddr, "path", r.URL.Path)
http.Error(w, `{"error":"invalid token"}`, http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
+38
View File
@@ -0,0 +1,38 @@
// Изменено: 2026-03-07
// Logging middleware — логирует каждый HTTP запрос через slog.
// Записывает метод, путь, статус и длительность.
package middleware
import (
"log/slog"
"net/http"
"time"
)
// responseWriter оборачивает http.ResponseWriter чтобы захватить статус ответа.
type responseWriter struct {
http.ResponseWriter
status int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.status = code
rw.ResponseWriter.WriteHeader(code)
}
// Logging возвращает middleware которое логирует все запросы через slog.
func Logging(log *slog.Logger, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
rw := &responseWriter{ResponseWriter: w, status: http.StatusOK}
next.ServeHTTP(rw, r)
log.Info("http",
"method", r.Method,
"path", r.URL.Path,
"status", rw.status,
"duration_ms", time.Since(start).Milliseconds(),
"remote", r.RemoteAddr,
)
})
}