From 35bce36222466af75bcfc65587d1332cc9086170 Mon Sep 17 00:00:00 2001 From: Valentin Doreau Date: Thu, 31 Oct 2024 20:42:28 +0100 Subject: [PATCH] Initial commit --- .gitignore | 1 + Makefile | 14 ++++++++ README.md | 18 +++++++++++ app_exporter.go | 72 ++++++++++++++++++++++++++++++++++++++++++ apps/cubebackup.go | 21 ++++++++++++ apps/forgejo.go | 21 ++++++++++++ apps/forgejo_runner.go | 21 ++++++++++++ apps/grafana.go | 21 ++++++++++++ apps/metrics.go | 56 ++++++++++++++++++++++++++++++++ apps/nextcloud.go | 25 +++++++++++++++ apps/odoo.go | 23 ++++++++++++++ apps/postgres.go | 21 ++++++++++++ apps/vaultwarden.go | 21 ++++++++++++ go.mod | 17 ++++++++++ go.sum | 24 ++++++++++++++ 15 files changed, 376 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 app_exporter.go create mode 100644 apps/cubebackup.go create mode 100644 apps/forgejo.go create mode 100644 apps/forgejo_runner.go create mode 100644 apps/grafana.go create mode 100644 apps/metrics.go create mode 100644 apps/nextcloud.go create mode 100644 apps/odoo.go create mode 100644 apps/postgres.go create mode 100644 apps/vaultwarden.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a5f5d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +app-exporter diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d368953 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +.PHONY: all build clean + +PROJECT_NAME = $(shell head -n1 go.mod | cut -d'/' -f2) + + +all: build + +# Build static executable +build: + CGO_ENABLED=0 go build + strip $(PROJECT_NAME) + +clean: + rm $(PROJECT_NAME) diff --git a/README.md b/README.md new file mode 100644 index 0000000..c2ab1ed --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Prometheus Application Exporter + +A Prometheus exporter to list application and their installed version. + +This is mainly targeted at the Recycled Cloud hosted applications. + +## Supported applications + +- cloudpanel: TODO +- cubebackup +- forgejo +- forgejo_runner +- grafana +- nextcloud: `curl -s https://files.rccd:9000/status.php | jq .version -r` +- odoo +- odoo enterprise commit/date: TODO +- postgres +- vaultwarden diff --git a/app_exporter.go b/app_exporter.go new file mode 100644 index 0000000..ef46d36 --- /dev/null +++ b/app_exporter.go @@ -0,0 +1,72 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "os" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "recycled.cloud/app-exporter/apps" +) + +const VERSION = "0.1.0" + +const LISTEN_ADDR = ":9404" +const INTERVAL = 24 * time.Hour + +var version_flag = flag.Bool("version", false, "Shows the program version") +var logfile = flag.String("logfile", "-", "Where to write the logs") + +func main() { + flag.Parse() + + if *version_flag { + fmt.Printf("%v\n", VERSION) + os.Exit(0) + } + + if *logfile != "-" { + f, err := os.OpenFile(*logfile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0664) + if err != nil { + log.Fatalf("Could not open logfile: %v\n", err) + } + defer f.Close() + log.SetOutput(f) + } + + reg := prometheus.NewRegistry() + + log.Printf("app-exporter version %v\n", VERSION) + + m := apps.NewMetrics(reg) + + go RecordMetrics(*m) + + http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg})) + + log.Printf("Listening on %v ...\n", LISTEN_ADDR) + + log.Fatal(http.ListenAndServe(LISTEN_ADDR, nil)) +} + +func RecordMetrics(m apps.Metrics) { + for { + m.Reset() + + apps.Cubebackup(m) + apps.Forgejo(m) + apps.ForgejoRunner(m) + apps.Grafana(m) + apps.Nextcloud(m) + apps.Odoo(m) + apps.Postgres(m) + apps.Vaultwarden(m) + + log.Printf("> Waiting %v\n", INTERVAL) + time.Sleep(INTERVAL) + } +} diff --git a/apps/cubebackup.go b/apps/cubebackup.go new file mode 100644 index 0000000..7ed9ddc --- /dev/null +++ b/apps/cubebackup.go @@ -0,0 +1,21 @@ +package apps + +import ( + "log" + "os/exec" + "strings" + + "github.com/prometheus/client_golang/prometheus" +) + +func Cubebackup(m Metrics) { + out, err := exec.Command("/opt/cubebackup365/bin/cbackup", "version").Output() + if err != nil { + log.Printf(">> WARN: Unable to get Cubebackup version: %v", err) + return + } + + version := strings.Split(string(out), " ")[1] + + m.Cubebackup.With(prometheus.Labels{"version": version}).Set(1) +} diff --git a/apps/forgejo.go b/apps/forgejo.go new file mode 100644 index 0000000..892a901 --- /dev/null +++ b/apps/forgejo.go @@ -0,0 +1,21 @@ +package apps + +import ( + "log" + "os/exec" + "strings" + + "github.com/prometheus/client_golang/prometheus" +) + +func Forgejo(m Metrics) { + out, err := exec.Command("forgejo", "--version").Output() + if err != nil { + log.Printf(">> WARN: Unable to get Forgejo version: %v", err) + return + } + + version := strings.Split(string(out), " ")[2] + + m.Forgejo.With(prometheus.Labels{"version": version}).Set(1) +} diff --git a/apps/forgejo_runner.go b/apps/forgejo_runner.go new file mode 100644 index 0000000..af49c4a --- /dev/null +++ b/apps/forgejo_runner.go @@ -0,0 +1,21 @@ +package apps + +import ( + "log" + "os/exec" + "strings" + + "github.com/prometheus/client_golang/prometheus" +) + +func ForgejoRunner(m Metrics) { + out, err := exec.Command("forgejo_runner", "--version").Output() + if err != nil { + log.Printf(">> WARN: Unable to get Forgejo_runner version: %v", err) + return + } + + version := strings.Split(string(out), " ")[2] + + m.ForgejoRunner.With(prometheus.Labels{"version": version}).Set(1) +} diff --git a/apps/grafana.go b/apps/grafana.go new file mode 100644 index 0000000..dc99cf3 --- /dev/null +++ b/apps/grafana.go @@ -0,0 +1,21 @@ +package apps + +import ( + "log" + "os/exec" + "strings" + + "github.com/prometheus/client_golang/prometheus" +) + +func Grafana(m Metrics) { + out, err := exec.Command("grafana-cli", "--version").Output() + if err != nil { + log.Printf(">> WARN: Unable to get Grafana version: %v", err) + return + } + + version := strings.Split(string(out), " ")[2] + + m.Grafana.With(prometheus.Labels{"version": version}).Set(1) +} diff --git a/apps/metrics.go b/apps/metrics.go new file mode 100644 index 0000000..fd2ec76 --- /dev/null +++ b/apps/metrics.go @@ -0,0 +1,56 @@ +package apps + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +type Metrics struct { + Cubebackup prometheus.GaugeVec + Forgejo prometheus.GaugeVec + ForgejoRunner prometheus.GaugeVec + Grafana prometheus.GaugeVec + Nextcloud prometheus.GaugeVec + Odoo prometheus.GaugeVec + Postgres prometheus.GaugeVec + Vaultwarden prometheus.GaugeVec +} + +func NewMetrics(reg prometheus.Registerer) *Metrics { + metrics := &Metrics{ + Cubebackup: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "app", Subsystem: "version", Name: "cubebackup", Help: ""}, []string{"version"}), + Forgejo: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "app", Subsystem: "version", Name: "forgejo", Help: ""}, []string{"version"}), + ForgejoRunner: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "app", Subsystem: "version", Name: "forgejo_runner", Help: ""}, []string{"version"}), + Grafana: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "app", Subsystem: "version", Name: "grafana", Help: ""}, []string{"version"}), + Nextcloud: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "app", Subsystem: "version", Name: "nextcloud", Help: ""}, []string{"version"}), + Odoo: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "app", Subsystem: "version", Name: "odoo", Help: ""}, []string{"version", "version_date"}), + Postgres: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "app", Subsystem: "version", Name: "postgres", Help: ""}, []string{"version"}), + Vaultwarden: *prometheus.NewGaugeVec(prometheus.GaugeOpts{Namespace: "app", Subsystem: "version", Name: "vaultwarden", Help: ""}, []string{"version"}), + } + + reg.MustRegister( + metrics.Cubebackup, + metrics.Forgejo, + metrics.ForgejoRunner, + metrics.Grafana, + metrics.Nextcloud, + metrics.Odoo, + metrics.Postgres, + metrics.Vaultwarden, + ) + + return metrics +} + +// Reset all metrics +// +// This should be called on every loop iteration. +func (m *Metrics) Reset() { + m.Cubebackup.Reset() + m.Forgejo.Reset() + m.ForgejoRunner.Reset() + m.Grafana.Reset() + m.Nextcloud.Reset() + m.Odoo.Reset() + m.Postgres.Reset() + m.Vaultwarden.Reset() +} diff --git a/apps/nextcloud.go b/apps/nextcloud.go new file mode 100644 index 0000000..a940e77 --- /dev/null +++ b/apps/nextcloud.go @@ -0,0 +1,25 @@ +package apps + +import ( + "encoding/json" + "net/http" + + "github.com/prometheus/client_golang/prometheus" +) + +type apiResponse struct { + version string +} + +func Nextcloud(m Metrics) { + var resp, err = http.Get("http://localhost:9000/status.php") + if err != nil { + return + } + defer resp.Body.Close() + + var res = apiResponse{} + json.NewDecoder(resp.Body).Decode(&res) + + m.Nextcloud.With(prometheus.Labels{"version": res.version}).Set(1) +} diff --git a/apps/odoo.go b/apps/odoo.go new file mode 100644 index 0000000..8b0180e --- /dev/null +++ b/apps/odoo.go @@ -0,0 +1,23 @@ +package apps + +import ( + "log" + "os/exec" + "strings" + + "github.com/prometheus/client_golang/prometheus" +) + +func Odoo(m Metrics) { + out, err := exec.Command("odoo", "--version").Output() + if err != nil { + log.Printf(">> WARN: Unable to get Odoo version: %v", err) + return + } + + full_version := strings.Split(string(out), " ")[2] + version := strings.Split(full_version, "-")[0] + version_date := strings.TrimSpace(strings.Split(full_version, "-")[1]) + + m.Odoo.With(prometheus.Labels{"version": version, "version_date": version_date}).Set(1) +} diff --git a/apps/postgres.go b/apps/postgres.go new file mode 100644 index 0000000..6046eb5 --- /dev/null +++ b/apps/postgres.go @@ -0,0 +1,21 @@ +package apps + +import ( + "log" + "os/exec" + "strings" + + "github.com/prometheus/client_golang/prometheus" +) + +func Postgres(m Metrics) { + out, err := exec.Command("psql", "--version").Output() + if err != nil { + log.Printf(">> WARN: Unable to get Postgres version: %v", err) + return + } + + version := strings.Split(string(out), " ")[2] + + m.Postgres.With(prometheus.Labels{"version": version}).Set(1) +} diff --git a/apps/vaultwarden.go b/apps/vaultwarden.go new file mode 100644 index 0000000..d0c4161 --- /dev/null +++ b/apps/vaultwarden.go @@ -0,0 +1,21 @@ +package apps + +import ( + "encoding/json" + "net/http" + + "github.com/prometheus/client_golang/prometheus" +) + +func Vaultwarden(m Metrics) { + var resp, err = http.Get("http://localhost:8080/api/version") + if err != nil { + return + } + defer resp.Body.Close() + + var version = new(string) + json.NewDecoder(resp.Body).Decode(version) + + m.Vaultwarden.With(prometheus.Labels{"version": *version}).Set(1) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d8aecb8 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module recycled.cloud/app-exporter + +go 1.23.2 + +require github.com/prometheus/client_golang v1.20.5 + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + golang.org/x/sys v0.22.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d5318cf --- /dev/null +++ b/go.sum @@ -0,0 +1,24 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=