add env for interval and exclude containers

This commit is contained in:
Simon Rieger 2025-07-21 10:39:29 +02:00
parent 23e78f2afe
commit 7530cc18eb
2 changed files with 76 additions and 33 deletions

View file

@ -5,6 +5,9 @@ services:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
#ports: #ports:
# - "9788:9788" # - "9788:9788"
environment:
- CHECK_INTERVAL=6h
- EXCLUDE_CONTAINERS=immich_redis,mailcowdockerized-ofelia-mailcow-1
restart: always restart: always
networks: networks:
default: default:

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"os"
"regexp" "regexp"
"strings" "strings"
"sync" "sync"
@ -35,10 +36,19 @@ type StatusCache struct {
} }
var ( var (
cache = &StatusCache{Data: []ImageStatus{}, LastCheck: time.Time{}} cache = &StatusCache{Data: []ImageStatus{}, LastCheck: time.Time{}}
interval = 6 * time.Hour interval = 6 * time.Hour // Default wird ggf. überschrieben durch CHECK_INTERVAL
excludeContainers = map[string]struct{}{}
) )
// Helper: image ohne ":tag" erhält "latest"
func normalizeImageTag(tag string) string {
if !strings.Contains(tag, ":") {
return tag + ":latest"
}
return tag
}
func toRegistryImage(imageTag string) (string, error) { func toRegistryImage(imageTag string) (string, error) {
r := regexp.MustCompile(`^(?:(?P<registry>[^/]+)/)?(?P<repo>[^:]+)(?::(?P<tag>.+))?$`) r := regexp.MustCompile(`^(?:(?P<registry>[^/]+)/)?(?P<repo>[^:]+)(?::(?P<tag>.+))?$`)
match := r.FindStringSubmatch(imageTag) match := r.FindStringSubmatch(imageTag)
@ -66,13 +76,6 @@ func extractDigest(s string) string {
return s return s
} }
func normalizeImageTag(tag string) string {
if !strings.Contains(tag, ":") {
return tag + ":latest"
}
return tag
}
func checkImageUpdates() { func checkImageUpdates() {
ctx := context.Background() ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv) cli, err := client.NewClientWithOpts(client.FromEnv)
@ -107,55 +110,51 @@ func checkImageUpdates() {
} }
} }
} }
// Debug-Ausgabe aller bekannten imageTagToDigest
fmt.Println("DEBUG: Alle bekannten imageTagToDigest:")
for k, v := range imageTagToDigest {
fmt.Printf(" %s -> %s\n", k, v)
}
results := make([]ImageStatus, 0) results := make([]ImageStatus, 0)
for _, ctr := range containers { for _, ctr := range containers {
rawTag := ctr.Image
tag := normalizeImageTag(rawTag)
containerName := "unknown" containerName := "unknown"
if len(ctr.Names) > 0 { if len(ctr.Names) > 0 {
containerName = ctr.Names[0] containerName = strings.TrimPrefix(ctr.Names[0], "/")
} }
imageRef, err := toRegistryImage(tag) // Überspringen, wenn im EXCLUDE_CONTAINERS enthalten
if err != nil { if _, excluded := excludeContainers[containerName]; excluded {
log.Printf("Fehler beim Parsen des Image-Namens (%s): %v", tag, err) fmt.Printf("⏭️ Container '%s' ist ausgeschlossen.\n", containerName)
continue continue
} }
localDigest := imageTagToDigest[tag] rawTag := normalizeImageTag(ctr.Image)
localDigest := imageTagToDigest[rawTag]
imageRef, err := toRegistryImage(rawTag)
if err != nil {
log.Printf("Ungültige Image-Referenz (%s): %v", rawTag, err)
continue
}
refObj, err := ref.New(imageRef) refObj, err := ref.New(imageRef)
if err != nil { if err != nil {
log.Printf("Ungültige Image-Referenz (%s): %v", tag, err) log.Printf("Fehler beim Erzeugen der Referenz (%s): %v", rawTag, err)
continue continue
} }
desc, err := rc.ManifestHead(ctx, refObj) desc, err := rc.ManifestHead(ctx, refObj)
if err != nil { if err != nil {
log.Printf("Fehler beim Abrufen des Remote-Manifests (%s): %v", tag, err) log.Printf("Remote-Manifest nicht gefunden (%s): %v", rawTag, err)
continue continue
} }
remoteDigest := desc.GetDigest().String()
remoteDigest := desc.GetDigest().String()
update := 0.0 update := 0.0
if localDigest != remoteDigest { if localDigest != remoteDigest {
update = 1.0 update = 1.0
fmt.Printf("Container: %s\n Image: %s\n Local Digest: %s\n Remote Digest: %s\n -> Update verfügbar!\n", fmt.Printf("⚠️ Container: %s\n -> Image: %s\n -> Update verfügbar!\n", containerName, rawTag)
containerName, tag, localDigest, remoteDigest)
} else { } else {
fmt.Printf("Container: %s\n Image: %s\n Local Digest: %s\n Remote Digest: %s\n -> Kein Update verfügbar.\n", fmt.Printf("✅ Container: %s\n -> Image: %s\n -> Aktuell\n", containerName, rawTag)
containerName, tag, localDigest, remoteDigest)
} }
imageName, imageTag := tag, "latest" imageName, imageTag := rawTag, "latest"
if cp := strings.Split(tag, ":"); len(cp) == 2 { if cp := strings.Split(rawTag, ":"); len(cp) == 2 {
imageName, imageTag = cp[0], cp[1] imageName, imageTag = cp[0], cp[1]
} }
@ -205,8 +204,40 @@ func (c *imageUpdateCollector) Collect(ch chan<- prometheus.Metric) {
} }
} }
func loadIntervalFromEnv() {
if val := os.Getenv("CHECK_INTERVAL"); val != "" {
dur, err := time.ParseDuration(val)
if err != nil {
log.Printf("❌ Ungültiger CHECK_INTERVAL: %s verwende Default (%s)", val, interval)
} else {
interval = dur
}
}
}
func loadExclusionsFromEnv() {
raw := os.Getenv("EXCLUDE_CONTAINERS")
if raw == "" {
return
}
list := strings.Split(raw, ",")
for _, name := range list {
clean := strings.TrimSpace(name)
if clean != "" {
excludeContainers[clean] = struct{}{}
}
}
}
func main() { func main() {
log.Printf("🚀 Docker Image Update Exporter gestartet Intervall: %v", interval) log.Println("🚀 Docker Image Update Exporter startet...")
loadIntervalFromEnv()
loadExclusionsFromEnv()
log.Printf("🔁 Prüfintervall: %v", interval)
if len(excludeContainers) > 0 {
log.Printf("🙈 Ignorierte Container: %v", keys(excludeContainers))
}
go func() { go func() {
for { for {
@ -214,6 +245,7 @@ func main() {
time.Sleep(interval) time.Sleep(interval)
} }
}() }()
checkImageUpdates() checkImageUpdates()
exporter := newImageUpdateCollector() exporter := newImageUpdateCollector()
@ -222,3 +254,11 @@ func main() {
http.Handle("/metrics", promhttp.Handler()) http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":9788", nil)) log.Fatal(http.ListenAndServe(":9788", nil))
} }
func keys(m map[string]struct{}) []string {
var result []string
for k := range m {
result = append(result, k)
}
return result
}