WIP: sneak/next #21

Draft
sneak wants to merge 6 commits from sneak/next into master
11 changed files with 123 additions and 68 deletions
Showing only changes of commit 3f49d528e7 - Show all commits

View File

@ -5,7 +5,9 @@ import (
"git.eeqj.de/sneak/gohttpserver/internal/database" "git.eeqj.de/sneak/gohttpserver/internal/database"
"git.eeqj.de/sneak/gohttpserver/internal/globals" "git.eeqj.de/sneak/gohttpserver/internal/globals"
"git.eeqj.de/sneak/gohttpserver/internal/handlers" "git.eeqj.de/sneak/gohttpserver/internal/handlers"
"git.eeqj.de/sneak/gohttpserver/internal/healthcheck"
"git.eeqj.de/sneak/gohttpserver/internal/logger" "git.eeqj.de/sneak/gohttpserver/internal/logger"
"git.eeqj.de/sneak/gohttpserver/internal/middleware"
"git.eeqj.de/sneak/gohttpserver/internal/server" "git.eeqj.de/sneak/gohttpserver/internal/server"
"go.uber.org/fx" "go.uber.org/fx"
) )
@ -29,6 +31,8 @@ func main() {
handlers.New, handlers.New,
logger.New, logger.New,
server.New, server.New,
middleware.New,
healthcheck.New,
), ),
fx.Invoke(func(*server.Server) {}), fx.Invoke(func(*server.Server) {}),
).Run() ).Run()

View File

@ -20,8 +20,9 @@ import (
) )
type ConfigParams struct { type ConfigParams struct {
Globals globals.Globals fx.In
Logger logger.Logger Globals *globals.Globals
Logger *logger.Logger
} }
type Config struct { type Config struct {
@ -32,7 +33,7 @@ type Config struct {
MetricsUsername string MetricsUsername string
Port int Port int
SentryDSN string SentryDSN string
params ConfigParams params *ConfigParams
log *zerolog.Logger log *zerolog.Logger
} }
@ -77,6 +78,7 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
MetricsUsername: viper.GetString("METRICS_USERNAME"), MetricsUsername: viper.GetString("METRICS_USERNAME"),
MetricsPassword: viper.GetString("METRICS_PASSWORD"), MetricsPassword: viper.GetString("METRICS_PASSWORD"),
log: log, log: log,
params: &params,
} }
if s.Debug { if s.Debug {

View File

@ -20,19 +20,19 @@ import (
type DatabaseParams struct { type DatabaseParams struct {
fx.In fx.In
Logger logger.Logger Logger *logger.Logger
Config config.Config Config *config.Config
} }
type Database struct { type Database struct {
URL string URL string
log *zerolog.Logger log *zerolog.Logger
params DatabaseParams params *DatabaseParams
} }
func New(lc fx.Lifecycle, params DatabaseParams) (*Database, error) { func New(lc fx.Lifecycle, params DatabaseParams) (*Database, error) {
s := new(Database) s := new(Database)
s.params = params s.params = &params
s.log = params.Logger.Get() s.log = params.Logger.Get()
s.log.Info().Msg("Database instantiated") s.log.Info().Msg("Database instantiated")

View File

@ -2,29 +2,11 @@ package handlers
import ( import (
"net/http" "net/http"
"time"
) )
func (s *Handlers) handleHealthCheck() http.HandlerFunc { func (s *Handlers) HandleHealthCheck() http.HandlerFunc {
type response struct {
Status string `json:"status"`
Now string `json:"now"`
UptimeSeconds int64 `json:"uptime_seconds"`
UptimeHuman string `json:"uptime_human"`
Version string `json:"version"`
Appname string `json:"appname"`
Maintenance bool `json:"maintenance_mode"`
}
return func(w http.ResponseWriter, req *http.Request) { return func(w http.ResponseWriter, req *http.Request) {
resp := &response{ resp := s.hc.Healthcheck()
Status: "ok",
Now: time.Now().UTC().Format(time.RFC3339Nano),
UptimeSeconds: int64(s.uptime().Seconds()),
UptimeHuman: s.params.Server.uptime().String(),
Maintenance: s.params.Server.MaintenanceMode(),
Appname: s.params.Globals.Appname,
Version: s.params.Globals.Version,
}
s.respondJSON(w, req, resp, 200) s.respondJSON(w, req, resp, 200)
} }
} }

View File

@ -13,7 +13,7 @@ func (s *Handlers) HandleIndex() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
err := indexTemplate.ExecuteTemplate(w, "index", nil) err := indexTemplate.ExecuteTemplate(w, "index", nil)
if err != nil { if err != nil {
s.log.Println(err.Error()) s.log.Error().Err(err).Msg("")
http.Error(w, http.StatusText(500), 500) http.Error(w, http.StatusText(500), 500)
} }
} }

View File

@ -7,6 +7,7 @@ import (
"git.eeqj.de/sneak/gohttpserver/internal/database" "git.eeqj.de/sneak/gohttpserver/internal/database"
"git.eeqj.de/sneak/gohttpserver/internal/globals" "git.eeqj.de/sneak/gohttpserver/internal/globals"
"git.eeqj.de/sneak/gohttpserver/internal/healthcheck"
"git.eeqj.de/sneak/gohttpserver/internal/logger" "git.eeqj.de/sneak/gohttpserver/internal/logger"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"go.uber.org/fx" "go.uber.org/fx"
@ -14,20 +15,23 @@ import (
type HandlersParams struct { type HandlersParams struct {
fx.In fx.In
Logger logger.Logger Logger *logger.Logger
Globals globals.Globals Globals *globals.Globals
Database database.Database Database *database.Database
Healthcheck *healthcheck.Healthcheck
} }
type Handlers struct { type Handlers struct {
params HandlersParams params *HandlersParams
log *zerolog.Logger log *zerolog.Logger
hc *healthcheck.Healthcheck
} }
func New(lc fx.Lifecycle, params HandlersParams) (*Handlers, error) { func New(lc fx.Lifecycle, params HandlersParams) (*Handlers, error) {
s := new(Handlers) s := new(Handlers)
s.params = params s.params = &params
s.log = params.Logger.Get() s.log = params.Logger.Get()
s.hc = params.Healthcheck
lc.Append(fx.Hook{ lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error { OnStart: func(ctx context.Context) error {
// FIXME compile some templates here or something // FIXME compile some templates here or something

View File

@ -0,0 +1,71 @@
package healthcheck
import (
"context"
"time"
"git.eeqj.de/sneak/gohttpserver/internal/config"
"git.eeqj.de/sneak/gohttpserver/internal/database"
"git.eeqj.de/sneak/gohttpserver/internal/globals"
"git.eeqj.de/sneak/gohttpserver/internal/logger"
"github.com/rs/zerolog"
"go.uber.org/fx"
)
type HealthcheckParams struct {
fx.In
Globals *globals.Globals
Config *config.Config
Logger *logger.Logger
Database *database.Database
}
type Healthcheck struct {
StartupTime time.Time
log *zerolog.Logger
params *HealthcheckParams
}
func New(lc fx.Lifecycle, params HealthcheckParams) (*Healthcheck, error) {
s := new(Healthcheck)
s.params = &params
s.log = params.Logger.Get()
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
s.StartupTime = time.Now()
return nil
},
OnStop: func(ctx context.Context) error {
// FIXME do server shutdown here
return nil
},
})
return s, nil
}
type HealthcheckResponse struct {
Status string `json:"status"`
Now string `json:"now"`
UptimeSeconds int64 `json:"uptime_seconds"`
UptimeHuman string `json:"uptime_human"`
Version string `json:"version"`
Appname string `json:"appname"`
Maintenance bool `json:"maintenance_mode"`
}
func (s *Healthcheck) uptime() time.Duration {
return time.Since(s.StartupTime)
}
func (s *Healthcheck) Healthcheck() *HealthcheckResponse {
resp := &HealthcheckResponse{
Status: "ok",
Now: time.Now().UTC().Format(time.RFC3339Nano),
UptimeSeconds: int64(s.uptime().Seconds()),
UptimeHuman: s.uptime().String(),
Appname: s.params.Globals.Appname,
Version: s.params.Globals.Version,
}
return resp
}

View File

@ -12,7 +12,7 @@ import (
type LoggerParams struct { type LoggerParams struct {
fx.In fx.In
Globals globals.Globals Globals *globals.Globals
} }
type Logger struct { type Logger struct {

View File

@ -21,19 +21,19 @@ import (
type MiddlewareParams struct { type MiddlewareParams struct {
fx.In fx.In
Logger logger.Logger Logger *logger.Logger
Globals globals.Globals Globals *globals.Globals
Config config.Config Config *config.Config
} }
type Middleware struct { type Middleware struct {
log *zerolog.Logger log *zerolog.Logger
params MiddlewareParams params *MiddlewareParams
} }
func New(lc fx.Lifecycle, params MiddlewareParams) (*Middleware, error) { func New(lc fx.Lifecycle, params MiddlewareParams) (*Middleware, error) {
s := new(Middleware) s := new(Middleware)
s.params = params s.params = &params
s.log = params.Logger.Get() s.log = params.Logger.Get()
return s, nil return s, nil
} }

View File

@ -57,33 +57,33 @@ func (s *Server) SetupRoutes() {
// complete docs: https://github.com/go-chi/chi // complete docs: https://github.com/go-chi/chi
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
s.router.Get("/", s.h.handleIndex()) s.router.Get("/", s.h.HandleIndex())
s.router.Mount("/s", http.StripPrefix("/s", http.FileServer(http.FS(static.Static)))) s.router.Mount("/s", http.StripPrefix("/s", http.FileServer(http.FS(static.Static))))
s.router.Route("/api/v1", func(r chi.Router) { s.router.Route("/api/v1", func(r chi.Router) {
r.Get("/now", s.h.handleNow()) r.Get("/now", s.h.HandleNow())
}) })
// if you want to use a general purpose middleware (http.Handler // if you want to use a general purpose middleware (http.Handler
// wrapper) on a specific HandleFunc route, you need to take the // wrapper) on a specific HandleFunc route, you need to take the
// .ServeHTTP of the http.Handler to get its HandleFunc, viz: // .ServeHTTP of the http.Handler to get its HandleFunc, viz:
authMiddleware := s.AuthMiddleware() authMiddleware := s.mw.AuthMiddleware()
s.router.Get( s.router.Get(
"/login", "/login",
authMiddleware(s.h.handleLogin()).ServeHTTP, authMiddleware(s.h.HandleLogin()).ServeHTTP,
) )
// route that panics for testing // route that panics for testing
// CHANGEME remove this // CHANGEME remove this
s.router.Get( s.router.Get(
"/panic", "/panic",
s.h.handlePanic(), s.h.HandlePanic(),
) )
s.router.Get( s.router.Get(
"/.well-known/healthcheck.json", "/.well-known/healthcheck.json",
s.h.handleHealthCheck(), s.h.HandleHealthCheck(),
) )
// set up authenticated /metrics route: // set up authenticated /metrics route:

View File

@ -3,7 +3,6 @@ package server
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
@ -16,7 +15,6 @@ import (
"git.eeqj.de/sneak/gohttpserver/internal/logger" "git.eeqj.de/sneak/gohttpserver/internal/logger"
"git.eeqj.de/sneak/gohttpserver/internal/middleware" "git.eeqj.de/sneak/gohttpserver/internal/middleware"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/spf13/viper"
"go.uber.org/fx" "go.uber.org/fx"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
@ -34,17 +32,14 @@ import (
type ServerParams struct { type ServerParams struct {
fx.In fx.In
Logger logger.Logger Logger *logger.Logger
Globals globals.Globals Globals *globals.Globals
Config config.Config Config *config.Config
Middleware middleware.Middleware Middleware *middleware.Middleware
Handlers handlers.Handlers Handlers *handlers.Handlers
} }
type Server struct { type Server struct {
appname string
version string
buildarch string
startupTime time.Time startupTime time.Time
port int port int
exitCode int exitCode int
@ -55,8 +50,8 @@ type Server struct {
httpServer *http.Server httpServer *http.Server
router *chi.Mux router *chi.Mux
params ServerParams params ServerParams
mw middleware.Middleware mw *middleware.Middleware
h handlers.Handlers h *handlers.Handlers
} }
func New(lc fx.Lifecycle, params ServerParams) (*Server, error) { func New(lc fx.Lifecycle, params ServerParams) (*Server, error) {
@ -69,11 +64,12 @@ func New(lc fx.Lifecycle, params ServerParams) (*Server, error) {
lc.Append(fx.Hook{ lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error { OnStart: func(ctx context.Context) error {
s.startupTime = time.Now() s.startupTime = time.Now()
s.version = params.Globals.Version s.Run() // background FIXME
s.Run() return nil
}, },
OnStop: func(ctx context.Context) error { OnStop: func(ctx context.Context) error {
// FIXME do server shutdown here // FIXME do server shutdown here
return nil
}, },
}) })
return s, nil return s, nil
@ -93,22 +89,22 @@ func (s *Server) Run() {
// logging before sentry, because sentry logs // logging before sentry, because sentry logs
s.enableSentry() s.enableSentry()
return s.serve() s.serve() // FIXME deal with return value
} }
func (s *Server) enableSentry() { func (s *Server) enableSentry() {
s.sentryEnabled = false s.sentryEnabled = false
if s.Config.SentryDSN == "" { if s.params.Config.SentryDSN == "" {
return return
} }
err := sentry.Init(sentry.ClientOptions{ err := sentry.Init(sentry.ClientOptions{
Dsn: viper.GetString("SENTRY_DSN"), Dsn: s.params.Config.SentryDSN,
Release: fmt.Sprintf("%s-%s", s.params.Globals.Appname, s.params.Globals.Version), Release: fmt.Sprintf("%s-%s", s.params.Globals.Appname, s.params.Globals.Version),
}) })
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("sentry init failure") s.log.Fatal().Err(err).Msg("sentry init failure")
return return
} }
s.log.Info().Msg("sentry error reporting activated") s.log.Info().Msg("sentry error reporting activated")
@ -170,11 +166,7 @@ func (s *Server) cleanShutdown() {
} }
} }
func (s *Server) uptime() time.Duration { func (s *Server) MaintenanceMode() bool {
return time.Since(s.startupTime)
}
func (s *Server) maintenance() bool {
return s.params.Config.MaintenanceMode return s.params.Config.MaintenanceMode
} }