aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2026-01-17 13:07:17 +0100
committerMartin Fischer <martin@push-f.com>2026-01-17 19:40:49 +0100
commit6bff1a8e74e4eba3fae3ac3d0d6a3bd28a00ef3c (patch)
tree203ea8c97dd5111554c824f9e2fbd358bccb581f
parent7750ec9ab103d3687553a6bbcb25d676b985edcd (diff)
break: take --repos-dir instead of --repos
Because I want to be able to configure which repos are exported in the repo configs, which is implemented in the next commit.
-rw-r--r--main.go49
-rw-r--r--main_test.go11
-rw-r--r--service.nix6
3 files changed, 45 insertions, 21 deletions
diff --git a/main.go b/main.go
index 2e0e2cc..66a4b2c 100644
--- a/main.go
+++ b/main.go
@@ -10,6 +10,7 @@ import (
"net/http"
"os"
"os/exec"
+ "path"
"regexp"
"slices"
"strings"
@@ -21,7 +22,7 @@ import (
type Args struct {
Port int `help:"Port to listen on" short:"p" default:"8888" env:"PORT"`
- Repos StringMap `placeholder:"{\"name\": \"path\", ...}" required:"" help:"Repositories to be grepped" env:"REPOS"`
+ ReposDir string `required:"" help:"Directory containing repositories to be grepped" env:"REPOS_DIR"`
Patterns StringMap `placeholder:"{\"name\": \"regex\", ...}" required:"" help:"Patterns to be grepped for" env:"PATTERNS"`
}
@@ -40,32 +41,29 @@ func main() {
var args Args
ctx := kong.Parse(&args, kong.Description(
- "Prometheus metrics for how many lines in certain git repos match certain regular expressions.\n\n"+
+ "Prometheus metrics for how many lines in git repos match certain regular expressions.\n\n"+
"Only the HEAD of the repositories will be grepped.",
))
- te, err := newGitGrepExporter(args.Repos, args.Patterns, prometheus.DefaultRegisterer)
+ te, err := newGitGrepExporter(args.ReposDir, args.Patterns, prometheus.DefaultRegisterer)
ctx.FatalIfErrorf(err)
prometheus.MustRegister(te)
http.Handle("/metrics", promhttp.Handler())
- slog.Info("starting", "repos", args.Repos, "patterns", args.Patterns)
+ slog.Info("starting", "reposDir", args.ReposDir, "patterns", args.Patterns)
slog.Info(fmt.Sprintf("serving metrics at http://localhost:%d/metrics", args.Port))
http.ListenAndServe(fmt.Sprintf(":%d", args.Port), nil)
}
type GitGrepExporter struct {
- repos map[string]string
+ reposDir string
descs map[string]*prometheus.Desc
regexes map[string]*regexp.Regexp
combinedPattern string
}
-func newGitGrepExporter(repos map[string]string, patterns map[string]string, reg prometheus.Registerer) (*GitGrepExporter, error) {
- if len(repos) == 0 {
- return nil, fmt.Errorf("no repositories specified")
- }
+func newGitGrepExporter(reposDir string, patterns map[string]string, reg prometheus.Registerer) (*GitGrepExporter, error) {
if len(patterns) == 0 {
return nil, fmt.Errorf("no patterns specified")
}
@@ -83,7 +81,7 @@ func newGitGrepExporter(repos map[string]string, patterns map[string]string, reg
regexes[name] = regexp.MustCompile(pattern)
}
- return &GitGrepExporter{repos, descs, regexes, strings.Join(slices.Collect(maps.Values(patterns)), "|")}, nil
+ return &GitGrepExporter{reposDir, descs, regexes, strings.Join(slices.Collect(maps.Values(patterns)), "|")}, nil
}
func (e *GitGrepExporter) Describe(ch chan<- *prometheus.Desc) {
@@ -93,15 +91,34 @@ func (e *GitGrepExporter) Describe(ch chan<- *prometheus.Desc) {
}
func (g *GitGrepExporter) Collect(ch chan<- prometheus.Metric) {
- for name, path := range g.repos {
- slog.Info("starting grep", "path", path)
- g.collectCountsForRepo(name, ch)
- slog.Info("finished grep", "path", path)
+ slog.Info("listing directory", "path", g.reposDir)
+ entries, err := os.ReadDir(g.reposDir)
+ if err != nil {
+ slog.Error("failed to read repos directory", "path", g.reposDir, "error", err)
+ for _, desc := range g.descs {
+ ch <- prometheus.NewInvalidMetric(desc, err)
+ }
+ return
+ }
+
+ for _, entry := range entries {
+ if !entry.IsDir() {
+ continue
+ }
+ repoPath := path.Join(g.reposDir, entry.Name())
+ logger := slog.With("path", repoPath)
+
+ logger.Info("starting grep")
+ g.collectCountsForRepo(entry.Name(), repoPath, ch)
+ if err != nil {
+ logger.Error("grep failed", "error", err)
+ continue
+ }
+ logger.Info("finished grep")
}
}
-func (g *GitGrepExporter) collectCountsForRepo(repo string, ch chan<- prometheus.Metric) {
- path := g.repos[repo]
+func (g *GitGrepExporter) collectCountsForRepo(repo string, path string, ch chan<- prometheus.Metric) {
output, err := run("git", "-C", path, "grep", "-hE", g.combinedPattern, "HEAD")
if err != nil {
diff --git a/main_test.go b/main_test.go
index 9fdecca..45a3ba5 100644
--- a/main_test.go
+++ b/main_test.go
@@ -2,6 +2,8 @@ package main
import (
"log"
+ "os"
+ "path"
"strings"
"testing"
@@ -11,10 +13,15 @@ import (
)
func TestGrepRepo(t *testing.T) {
- repos := map[string]string{"project1": "/srv/repos/project1"}
+ reposDir := t.TempDir()
+ err := os.Mkdir(path.Join(reposDir, "project1"), 0o755)
+ if err != nil {
+ t.Fatal(err)
+ }
+
patterns := map[string]string{"match": "\\bMATCH\\b", "match_start": "^MATCH"}
reg := prometheus.NewRegistry()
- te, err := newGitGrepExporter(repos, patterns, reg)
+ te, err := newGitGrepExporter(reposDir, patterns, reg)
if err != nil {
t.Fatal(err)
}
diff --git a/service.nix b/service.nix
index bc0bbdc..ba53972 100644
--- a/service.nix
+++ b/service.nix
@@ -16,8 +16,8 @@ in
type = lib.types.attrsOf lib.types.str;
};
- repos = lib.mkOption {
- type = lib.types.attrsOf lib.types.str;
+ reposDir = lib.mkOption {
+ type = lib.types.str;
};
extraGroups = lib.mkOption {
@@ -35,7 +35,7 @@ in
};
environment = {
PORT = toString cfg.port;
- REPOS = builtins.toJSON cfg.repos;
+ REPOS_DIR = cfg.reposDir;
PATTERNS = builtins.toJSON cfg.patterns;
};