mirror of
https://github.com/lukaszraczylo/ghtracker.git
synced 2026-10-12 00:45:38 +00:00
A new pr_open alert carries the head commit SHA of each open, non-draft, same-repository pull request. An action with auto enabled sends its webhook once for each pull request and head commit. An authors list limits which pull requests trigger it. The alert never changes repository health.
110 lines
3.0 KiB
Go
110 lines
3.0 KiB
Go
// Command ghtracker serves a dashboard and Prometheus metrics for a list of GitHub repositories.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io/fs"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/collectors"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
|
|
"github.com/lukaszraczylo/ghtracker/internal/collector"
|
|
"github.com/lukaszraczylo/ghtracker/internal/config"
|
|
"github.com/lukaszraczylo/ghtracker/internal/gh"
|
|
"github.com/lukaszraczylo/ghtracker/internal/metrics"
|
|
"github.com/lukaszraczylo/ghtracker/internal/web"
|
|
)
|
|
|
|
// distFS holds the built UI; dist/.gitkeep keeps the pattern valid before the first UI build.
|
|
//
|
|
//go:embed all:dist
|
|
var distFS embed.FS
|
|
|
|
// version is set at release time with -ldflags "-X main.version=...".
|
|
var version = "dev"
|
|
|
|
const (
|
|
httpTimeout = 30 * time.Second
|
|
shutdownTimeout = 10 * time.Second
|
|
headerTimeout = 10 * time.Second
|
|
)
|
|
|
|
func main() {
|
|
cfgPath := flag.String("config", "config.yaml", "path to the YAML config file")
|
|
showVersion := flag.Bool("version", false, "print the version and exit")
|
|
flag.Parse()
|
|
if *showVersion {
|
|
fmt.Println(version)
|
|
return
|
|
}
|
|
log := slog.New(slog.NewTextHandler(os.Stderr, nil))
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer stop()
|
|
if err := run(ctx, *cfgPath, log); err != nil {
|
|
stop()
|
|
log.Error("fatal", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func run(ctx context.Context, cfgPath string, log *slog.Logger) error {
|
|
cfg, err := config.Load(cfgPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hc := &http.Client{Timeout: httpTimeout}
|
|
auth, err := gh.NewAppAuth(cfg.GitHub.AppID, cfg.GitHub.PrivateKeyPEM, cfg.GitHub.APIURL, cfg.GitHub.InstallationID, hc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
client := gh.NewClient(cfg.GitHub.APIURL, auth, hc, cfg.MaxItems)
|
|
coll := collector.New(client, cfg, log)
|
|
|
|
reg := prometheus.NewRegistry()
|
|
reg.MustRegister(collectors.NewGoCollector(), collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
|
|
metrics.New(coll, client.RateLimitRemaining))
|
|
ui, err := fs.Sub(distFS, "dist")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
srv := web.New(coll, promhttp.HandlerFor(reg, promhttp.HandlerOpts{}), ui, *cfg.ManualRefresh, log)
|
|
|
|
if len(cfg.Actions) > 0 {
|
|
actions := web.NewActions(cfg.Actions, log, reg)
|
|
srv.WithActions(actions)
|
|
if auto := web.NewAutoRunner(actions); auto != nil {
|
|
coll.OnRefresh(auto.Run)
|
|
}
|
|
}
|
|
|
|
go coll.Run(ctx)
|
|
|
|
hs := &http.Server{Addr: cfg.Listen, Handler: srv.Handler(), ReadHeaderTimeout: headerTimeout}
|
|
errc := make(chan error, 1)
|
|
go func() { errc <- hs.ListenAndServe() }()
|
|
log.Info("listening", "version", version, "addr", cfg.Listen, "repos", len(cfg.Repos), "refresh", cfg.RefreshInterval)
|
|
|
|
select {
|
|
case err := <-errc:
|
|
return err
|
|
case <-ctx.Done():
|
|
}
|
|
sctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
|
defer cancel()
|
|
if err := hs.Shutdown(sctx); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|