Initial project snapshot
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
chimiddleware "github.com/go-chi/chi/v5/middleware"
|
||||
|
||||
"github.com/example/remote-access-platform/backend/internal/modules/auth"
|
||||
"github.com/example/remote-access-platform/backend/internal/modules/cluster"
|
||||
"github.com/example/remote-access-platform/backend/internal/modules/identitysource"
|
||||
"github.com/example/remote-access-platform/backend/internal/modules/node"
|
||||
"github.com/example/remote-access-platform/backend/internal/modules/nodeagent"
|
||||
"github.com/example/remote-access-platform/backend/internal/modules/organization"
|
||||
"github.com/example/remote-access-platform/backend/internal/modules/resource"
|
||||
"github.com/example/remote-access-platform/backend/internal/modules/sessionbroker"
|
||||
"github.com/example/remote-access-platform/backend/internal/modules/sessiongateway"
|
||||
"github.com/example/remote-access-platform/backend/internal/modules/worker"
|
||||
"github.com/example/remote-access-platform/backend/internal/platform/authority"
|
||||
"github.com/example/remote-access-platform/backend/internal/platform/config"
|
||||
"github.com/example/remote-access-platform/backend/internal/platform/httpserver"
|
||||
"github.com/example/remote-access-platform/backend/internal/platform/logging"
|
||||
"github.com/example/remote-access-platform/backend/internal/platform/module"
|
||||
postgresplatform "github.com/example/remote-access-platform/backend/internal/platform/postgres"
|
||||
redisplatform "github.com/example/remote-access-platform/backend/internal/platform/redis"
|
||||
"github.com/example/remote-access-platform/backend/internal/platform/secrets"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
cfg config.Config
|
||||
logger *slog.Logger
|
||||
httpServer *http.Server
|
||||
workers []backgroundRunner
|
||||
db closeFunc
|
||||
redis closeFunc
|
||||
}
|
||||
|
||||
type closeFunc func() error
|
||||
type backgroundRunner func(context.Context) error
|
||||
|
||||
func NewApp(ctx context.Context) (*App, error) {
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load config: %w", err)
|
||||
}
|
||||
|
||||
logger := logging.New(cfg.App.Env)
|
||||
|
||||
db, err := postgresplatform.Open(ctx, cfg.Postgres)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
redisClient, err := redisplatform.Open(ctx, cfg.Redis)
|
||||
if err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authorityVerifier, err := authority.NewVerifier(cfg.Installation)
|
||||
if err != nil {
|
||||
redisClient.Close()
|
||||
db.Close()
|
||||
return nil, fmt.Errorf("create installation authority verifier: %w", err)
|
||||
}
|
||||
|
||||
deps := module.Dependencies{
|
||||
Config: module.Config{
|
||||
App: cfg.App,
|
||||
Auth: cfg.Auth,
|
||||
Installation: cfg.Installation,
|
||||
DataPlane: cfg.DataPlane,
|
||||
Secret: cfg.Secret,
|
||||
Session: cfg.Session,
|
||||
Worker: cfg.Worker,
|
||||
WebSocket: cfg.WebSocket,
|
||||
},
|
||||
Infra: module.Infra{
|
||||
Logger: logger,
|
||||
DB: db,
|
||||
Redis: redisClient,
|
||||
},
|
||||
}
|
||||
|
||||
workerStore := worker.NewRedisStore(redisClient)
|
||||
workerService := worker.NewService(deps, workerStore)
|
||||
authStore := auth.NewPostgresStore(db)
|
||||
authTx := auth.NewPostgresTransactor(db)
|
||||
authService := auth.NewService(deps, authStore, authTx, authorityVerifier)
|
||||
var resourceSecretStore *secrets.ResourceSecretStore
|
||||
if cfg.Secret.EncryptionKeyBase64 != "" {
|
||||
secretEncryptor, err := secrets.NewEncryptor(cfg.Secret.EncryptionKeyBase64, cfg.Secret.EncryptionKeyID)
|
||||
if err != nil {
|
||||
redisClient.Close()
|
||||
db.Close()
|
||||
return nil, fmt.Errorf("create resource secret encryptor: %w", err)
|
||||
}
|
||||
resourceSecretStore = secrets.NewResourceSecretStore(db, secretEncryptor)
|
||||
}
|
||||
|
||||
brokerStore := sessionbroker.NewPostgresStore(db, authorityVerifier)
|
||||
brokerTx := sessionbroker.NewPostgresTransactor(db, authorityVerifier)
|
||||
liveStateStore := sessionbroker.NewRedisLiveStateStore(redisClient)
|
||||
brokerService := sessionbroker.NewService(deps, brokerStore, brokerTx, liveStateStore, workerService, resourceSecretStore)
|
||||
workerEvents := worker.NewEventProcessor(redisClient, brokerService)
|
||||
leaseMonitor := worker.NewLeaseMonitor(workerService, brokerService, cfg.Worker.StaleLeaseGracePeriod)
|
||||
|
||||
brokerModule := sessionbroker.NewModule(brokerService)
|
||||
authModule := auth.NewModule(deps, authService)
|
||||
clusterModule := cluster.NewModule(deps, authorityVerifier)
|
||||
organizationModule := organization.NewModule(deps)
|
||||
identitySourceModule := identitysource.NewModule(deps)
|
||||
resourceModule := resource.NewModule(deps, resourceSecretStore)
|
||||
nodeModule := node.NewModule(deps)
|
||||
nodeAgentModule := nodeagent.NewModule(deps)
|
||||
sessionGatewayModule := sessiongateway.NewModule(deps, brokerModule.Service(), workerService)
|
||||
|
||||
router := buildRouter(
|
||||
logger,
|
||||
authModule,
|
||||
clusterModule,
|
||||
organizationModule,
|
||||
identitySourceModule,
|
||||
resourceModule,
|
||||
brokerModule,
|
||||
nodeModule,
|
||||
nodeAgentModule,
|
||||
sessionGatewayModule,
|
||||
)
|
||||
|
||||
return &App{
|
||||
cfg: cfg,
|
||||
logger: logger,
|
||||
httpServer: httpserver.New(cfg.HTTP, router),
|
||||
workers: []backgroundRunner{workerEvents.Run, leaseMonitor.Run},
|
||||
db: func() error {
|
||||
db.Close()
|
||||
return nil
|
||||
},
|
||||
redis: redisClient.Close,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *App) Run(ctx context.Context) error {
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
a.logger.Info("http server starting", "addr", a.httpServer.Addr, "service", a.cfg.App.Name)
|
||||
if err := a.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
errCh <- nil
|
||||
}()
|
||||
|
||||
for _, runner := range a.workers {
|
||||
runner := runner
|
||||
go func() {
|
||||
if err := runner(ctx); err != nil {
|
||||
errCh <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
a.logger.Info("shutdown signal received")
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), a.cfg.HTTP.ShutdownTimeout)
|
||||
defer cancel()
|
||||
|
||||
if err := a.httpServer.Shutdown(shutdownCtx); err != nil {
|
||||
return fmt.Errorf("shutdown http server: %w", err)
|
||||
}
|
||||
|
||||
if err := a.redis(); err != nil {
|
||||
return fmt.Errorf("close redis: %w", err)
|
||||
}
|
||||
|
||||
if err := a.db(); err != nil {
|
||||
return fmt.Errorf("close postgres: %w", err)
|
||||
}
|
||||
|
||||
a.logger.Info("app stopped", "at", time.Now().UTC())
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildRouter(logger *slog.Logger, modules ...module.Module) http.Handler {
|
||||
router := chi.NewRouter()
|
||||
router.Use(chimiddleware.RequestID)
|
||||
router.Use(chimiddleware.RealIP)
|
||||
router.Use(chimiddleware.Recoverer)
|
||||
router.Use(chimiddleware.Timeout(60 * time.Second))
|
||||
router.Use(chimiddleware.Heartbeat("/healthz"))
|
||||
|
||||
router.Get("/readyz", func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("ready"))
|
||||
})
|
||||
|
||||
router.Route("/api/v1", func(r chi.Router) {
|
||||
for _, mod := range modules {
|
||||
logger.Info("register module routes", "module", mod.Name())
|
||||
mod.RegisterRoutes(r)
|
||||
}
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
Reference in New Issue
Block a user