shared-sqs: Этапы 5+6 — Admin API, auth middleware, graceful shutdown

This commit is contained in:
Naeel
2026-04-09 12:59:53 +03:00
parent 08053ca8e5
commit 073683250c
3 changed files with 351 additions and 80 deletions
+115 -68
View File
@@ -1,80 +1,127 @@
// app/cmd/goaws.go
// Entry point — shared-sqs server
// Updated: 2026-04-09 — добавлены TenantStore, admin token, graceful shutdown (Trap #13)
package main
import (
"flag"
"net/http"
"os"
"time"
"context"
"flag"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"shared-sqs/app/models"
"shared-sqs/app/conf"
"shared-sqs/app/gosqs"
"shared-sqs/app/models"
"shared-sqs/app/router"
"shared-sqs/app/tenant"
log "github.com/sirupsen/logrus"
"shared-sqs/app/conf"
"shared-sqs/app/gosqs"
"shared-sqs/app/router"
log "github.com/sirupsen/logrus"
)
func main() {
var filename string
var debug bool
var loglevel string
flag.StringVar(&filename, "config", "", "config file location + name")
flag.BoolVar(&debug, "debug", false, "set debug log level")
flag.StringVar(&loglevel, "loglevel", "info", "log level (default info)")
flag.Parse()
var configFile string
var adminToken string
var port string
var debug bool
var loglevel string
log.SetFormatter(&log.JSONFormatter{})
log.SetOutput(os.Stdout)
flag.StringVar(&configFile, "config", "", "config file location")
flag.StringVar(&adminToken, "admin-token", "", "admin API bearer token")
flag.StringVar(&port, "port", "4100", "listen port")
flag.BoolVar(&debug, "debug", false, "set debug log level")
flag.StringVar(&loglevel, "loglevel", "info", "log level (info, debug, warn, error)")
flag.Parse()
if debug {
log.SetLevel(log.DebugLevel)
} else {
level, err := log.ParseLevel(loglevel)
if err != nil {
log.SetLevel(log.InfoLevel)
log.Warnf("Failed to parse loglevel %v, defaulting to info", loglevel)
} else {
log.SetLevel(level)
}
}
log.SetFormatter(&log.JSONFormatter{})
log.SetOutput(os.Stdout)
env := "Local"
if flag.NArg() > 0 {
env = flag.Arg(0)
}
portNumbers := conf.LoadYamlConfig(filename, env)
if models.CurrentEnvironment.LogToFile {
filename := models.CurrentEnvironment.LogFile
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err == nil {
log.SetOutput(file)
} else {
log.Infof("Failed to log to file: %s, using default stderr", filename)
}
}
r := router.New()
quit := make(chan bool, 0)
go gosqs.PeriodicTasks(1*time.Second, quit)
if len(portNumbers) == 1 {
log.Warnf("GoAws listening on: 0.0.0.0:%s", portNumbers[0])
err := http.ListenAndServe("0.0.0.0:"+portNumbers[0], r)
log.Fatal(err)
} else if len(portNumbers) == 2 {
go func() {
log.Warnf("GoAws listening on: 0.0.0.0:%s", portNumbers[0])
err := http.ListenAndServe("0.0.0.0:"+portNumbers[0], r)
log.Fatal(err)
}()
log.Warnf("GoAws listening on: 0.0.0.0:%s", portNumbers[1])
err := http.ListenAndServe("0.0.0.0:"+portNumbers[1], r)
log.Fatal(err)
} else {
log.Fatal("Not enough or too many ports defined to start GoAws.")
}
if debug {
log.SetLevel(log.DebugLevel)
} else {
level, err := log.ParseLevel(loglevel)
if err != nil {
log.SetLevel(log.InfoLevel)
log.Warnf("Failed to parse loglevel %v, defaulting to info", loglevel)
} else {
log.SetLevel(level)
}
}
// Admin token: flag > env SHARED_SQS_ADMIN_TOKEN > fatal (Trap #13)
if adminToken == "" {
adminToken = os.Getenv("SHARED_SQS_ADMIN_TOKEN")
}
if adminToken == "" {
log.Fatal("admin token required: use --admin-token flag or SHARED_SQS_ADMIN_TOKEN env var")
}
// Загрузить конфиг (очереди, env — без SNS)
env := "Local"
if flag.NArg() > 0 {
env = flag.Arg(0)
}
conf.LoadYamlConfig(configFile, env)
if models.CurrentEnvironment.LogToFile {
filename := models.CurrentEnvironment.LogFile
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err == nil {
log.SetOutput(file)
} else {
log.Infof("Failed to log to file: %s, using default stdout", filename)
}
}
// Инициализация in-memory TenantStore
tenantStore := tenant.NewTenantStore()
// Роутер с tenant auth и admin API
r := router.New(tenantStore, adminToken)
// PeriodicTasks — visibility timeout, DLQ, deduplication
quit := make(chan bool)
go gosqs.PeriodicTasks(1*time.Second, quit)
// HTTP сервер с таймаутами
srv := &http.Server{
Addr: "0.0.0.0:" + port,
Handler: r,
ReadTimeout: 30 * time.Second,
WriteTimeout: 35 * time.Second, // чуть больше чем max WaitTimeSeconds (20s)
IdleTimeout: 60 * time.Second,
}
// Запуск в горутине для graceful shutdown
serverErr := make(chan error, 1)
go func() {
log.Infof("shared-sqs listening on 0.0.0.0:%s", port)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
serverErr <- err
}
}()
// Graceful shutdown по SIGTERM/SIGINT (Trap #13)
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
select {
case sig := <-sigCh:
log.Infof("Received signal %s, shutting down", sig)
case err := <-serverErr:
log.Fatalf("Server error: %v", err)
}
// Остановить PeriodicTasks
close(quit)
// Дать 10 секунд на завершение текущих HTTP запросов
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Errorf("Server shutdown error: %v", err)
}
log.Info("shared-sqs stopped")
}