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:
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user