pkgdash/cmd/pkgdash/main.go

241 lines
7.2 KiB
Go

package main
import (
"context"
"crypto/tls"
"embed"
"io/fs"
"net/http"
"time"
slog "go.unistack.org/micro/v4/logger/slog"
appconfig "git.unistack.org/unistack-org/pkgdash/internal/config"
"git.unistack.org/unistack-org/pkgdash/internal/database"
"git.unistack.org/unistack-org/pkgdash/internal/handler"
"git.unistack.org/unistack-org/pkgdash/internal/storage"
_ "git.unistack.org/unistack-org/pkgdash/internal/storage/sqlite"
"git.unistack.org/unistack-org/pkgdash/internal/worker"
pb "git.unistack.org/unistack-org/pkgdash/proto"
jsoncodec "go.unistack.org/micro-codec-json/v4"
jsonpbcodec "go.unistack.org/micro-codec-jsonpb/v4"
yamlcodec "go.unistack.org/micro-codec-yaml/v4"
envconfig "go.unistack.org/micro-config-env/v4"
fileconfig "go.unistack.org/micro-config-file/v4"
vaultconfig "go.unistack.org/micro-config-vault/v4"
victoriameter "go.unistack.org/micro-meter-victoriametrics/v4"
httpsrv "go.unistack.org/micro-server-http/v4"
healthhandler "go.unistack.org/micro-server-http/v4/handler/health"
meterhandler "go.unistack.org/micro-server-http/v4/handler/meter"
spahandler "go.unistack.org/micro-server-http/v4/handler/spa"
swaggerui "go.unistack.org/micro-server-http/v4/handler/swagger-ui"
"go.unistack.org/micro/v4"
"go.unistack.org/micro/v4/config"
"go.unistack.org/micro/v4/logger"
"go.unistack.org/micro/v4/meter"
"go.unistack.org/micro/v4/options"
"go.unistack.org/micro/v4/server"
rutil "go.unistack.org/micro/v4/util/reflect"
)
const appName = "pkgdash"
var (
BuildDate string = "now" // filled when build
AppVersion string = "latest" // filled when build
)
//go:generate rm -rf assets
//go:generate mkdir assets
//go:generate cp -vr ../../ui/dist/ui assets/
//go:embed assets/*
var assets embed.FS
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
logger.DefaultLogger = slog.NewLogger(logger.WithLevel(logger.DebugLevel))
if err := logger.DefaultLogger.Init(); err != nil {
logger.Fatal(ctx, "failed to init logger")
}
cfg := appconfig.NewConfig(appName, AppVersion) // create new empty config
vc := vaultconfig.NewConfig(
config.AllowFail(true), // that may be not exists
config.Struct(cfg), // load from vault
options.Codec(jsoncodec.NewCodec()), // vault config in json
config.BeforeLoad(func(ctx context.Context, c config.Config) error {
return c.Init(
vaultconfig.HTTPClient(&http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}),
vaultconfig.Address(cfg.Vault.Addr),
vaultconfig.Timeout(5*time.Second),
vaultconfig.Token(cfg.Vault.Token),
vaultconfig.Path(cfg.Vault.Path),
)
}),
)
if err := config.Load(ctx,
[]config.Config{
config.NewConfig( // load from defaults
config.Struct(cfg), // pass config struct
),
fileconfig.NewConfig( // load from file
config.AllowFail(true), // that may be not exists
config.Struct(cfg), // pass config struct
options.Codec(yamlcodec.NewCodec()), // file config in json
fileconfig.Path("./local.yaml"), // nearby file
),
envconfig.NewConfig( // load from environment
config.Struct(cfg), // pass config struct
),
vc,
}, config.LoadOverride(true),
); err != nil {
logger.Fatal(ctx, "failed to load config: %v", err)
}
if err := config.Validate(ctx, cfg); err != nil {
logger.Fatal(ctx, "failed to validate config: %v", err)
}
swaggerui.Config["url"] = "../service.swagger.yaml"
meter.DefaultMeter = victoriameter.NewMeter(
meter.Path(cfg.Meter.Path),
meter.WriteFDMetrics(true),
meter.WriteProcessMetrics(true),
options.Address(cfg.Meter.Addr),
)
svc := micro.NewService()
if err := svc.Init(
micro.Server(httpsrv.NewServer()),
micro.Name(cfg.Server.Name),
micro.Version(cfg.Server.Version),
); err != nil {
logger.Fatal(ctx, "failed to init service: %v", err)
}
assetsUI, err := fs.Sub(assets, "assets/ui")
if err != nil {
logger.Fatal(ctx, "failed to get assets: %v", err)
}
if err := svc.Server("http").Init(
options.Address(cfg.Server.Addr),
options.Name(cfg.Server.Name),
server.Version(cfg.Server.Version),
options.Codecs("application/json", jsonpbcodec.NewCodec()),
options.Address(cfg.Server.Addr),
options.Context(ctx),
httpsrv.PathHandler(http.MethodGet, "/ui/*", spahandler.Handler("/ui/", assetsUI)),
httpsrv.PathHandler(http.MethodHead, "/ui/*", spahandler.Handler("/ui/", assetsUI)),
httpsrv.PathHandler(http.MethodGet, "/swagger-ui/*", swaggerui.Handler("/swagger-ui")),
); err != nil {
logger.Fatal(ctx, "failed to init service: %v", err)
}
if err := database.ParseDSN(cfg.Database); err != nil {
logger.Fatal(ctx, "failed to init database: %v", err)
}
db, err := database.Connect(ctx, cfg.Database, logger.DefaultLogger)
if err != nil {
logger.Fatal(ctx, "failed to connect database: %v", err)
}
store, err := storage.NewStorage(cfg.Database.Type, db)
if err != nil {
logger.Fatal(ctx, "failed to init storage: %v", err)
}
h, err := handler.NewHandler(store)
if err != nil {
logger.Fatal(ctx, "failed to create handler: %v", err)
}
log := logger.NewLogger(
logger.WithLevel(logger.ParseLevel(cfg.Server.LoggerLevel)),
logger.WithCallerSkipCount(3),
)
if err := svc.Init(micro.Logger(log)); err != nil {
logger.Fatal(ctx, "failed to init service: %v", err)
}
if err := pb.RegisterPkgdashServiceServer(svc.Server("http"), h); err != nil {
logger.Fatal(ctx, "failed to register handler: %v", err)
}
intsvc := httpsrv.NewServer(
options.Codecs("application/json", jsoncodec.NewCodec()),
options.Address(cfg.Meter.Addr),
options.Context(ctx),
)
if err := intsvc.Init(); err != nil {
logger.Fatal(ctx, "failed to init http srv: %v", err)
}
if err := healthhandler.RegisterHealthServiceServer(intsvc, healthhandler.NewHandler()); err != nil {
logger.Fatal(ctx, "failed to set http handler: %v", err)
}
if err := meterhandler.RegisterMeterServiceServer(intsvc, meterhandler.NewHandler()); err != nil {
logger.Fatal(ctx, "failed to set http handler: %v", err)
}
if err := intsvc.Start(); err != nil {
logger.Fatal(ctx, "failed to run http srv: %v", err)
}
cw, err := vc.Watch(ctx, config.WatchCoalesce(true), config.WatchInterval(1*time.Second, 5*time.Second))
if err != nil {
logger.Fatal(ctx, "failed to watch config: %v", err)
}
defer func() {
if err := cw.Stop(); err != nil {
logger.Error(ctx, err.Error())
}
}()
go func() {
for {
changes, err := cw.Next()
if err != nil {
logger.Error(ctx, "failed to get config update: %v", err)
}
for k, v := range changes {
if err = rutil.SetFieldByPath(cfg, v, k); err != nil {
logger.Error(ctx, "failed to set config update: %v", err)
break
}
}
if err == nil {
for k := range changes {
switch k {
case "Server.LoggerLevel":
if lvl, ok := changes[k].(string); ok {
logger.Info(ctx, "logger level changed to %s", lvl)
logger.DefaultLogger.Level(logger.ParseLevel(lvl))
}
}
}
}
}
}()
go func() {
worker.Run(ctx, store, time.Duration(cfg.App.CheckInterval))
}()
if err = svc.Run(); err != nil {
logger.Fatal(ctx, "failed to run svc: %v", err)
}
}