add env for interval and exclude containers
This commit is contained in:
parent
23e78f2afe
commit
7530cc18eb
2 changed files with 76 additions and 33 deletions
|
@ -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:
|
||||||
|
|
106
go/main.go
106
go/main.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue