diff --git a/main.go b/main.go index 5359e0c..7403d5e 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "strings" "sync" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" "github.com/prometheus/client_golang/prometheus" @@ -17,7 +18,6 @@ import ( "github.com/regclient/regclient/types/ref" ) -// Hilfsfunktion: registry/repo:tag für regclient func toRegistryImage(imageTag string) (string, error) { r := regexp.MustCompile(`^(?:(?P[^/]+)/)?(?P[^:]+)(?::(?P.+))?$`) match := r.FindStringSubmatch(imageTag) @@ -36,7 +36,6 @@ func toRegistryImage(imageTag string) (string, error) { return fmt.Sprintf("%s/%s:%s", registry, repo, tag), nil } -// Extrahiere nur den sha256:digest func extractDigest(s string) string { for _, part := range strings.Split(s, "@") { if strings.HasPrefix(part, "sha256:") { @@ -54,8 +53,8 @@ func newImageUpdateCollector() *imageUpdateCollector { return &imageUpdateCollector{ metric: prometheus.NewDesc( "docker_image_update_available", - "Ob Update für das lokale Docker-Image für das Tag im Registry verfügbar ist (1=Update, 0=aktuell)", - []string{"image", "tag"}, + "Ob Update für das lokale Docker-Image des laufenden Containers für das Tag im Registry verfügbar ist (1=Update, 0=aktuell)", + []string{"container_name", "image", "tag"}, nil, ), } @@ -75,71 +74,80 @@ func (c *imageUpdateCollector) Collect(ch chan<- prometheus.Metric) { defer cli.Close() rc := regclient.New() + // Hole nur aktive (laufende) Container + containers, err := cli.ContainerList(ctx, container.ListOptions{All: false}) + if err != nil { + log.Printf("Fehler bei ContainerList: %v", err) + return + } + images, err := cli.ImageList(ctx, image.ListOptions{All: true}) if err != nil { log.Printf("Fehler bei ImageList: %v", err) return } - var wg sync.WaitGroup + imageTagToDigest := make(map[string]string) for _, img := range images { for _, tag := range img.RepoTags { - wg.Add(1) - go func(tag string, img image.Summary) { - defer wg.Done() - imageRef, err := toRegistryImage(tag) - if err != nil { - log.Printf("ImageRef-Fehler bei %s: %v", tag, err) - return - } - - // Lokalen Digest holen - var localDigest string - if len(img.RepoDigests) > 0 { - localDigest = extractDigest(img.RepoDigests[0]) - } else { - localDigest = img.ID - } - - // Remote Digest holen - refObj, err := ref.New(imageRef) - if err != nil { - log.Printf("ImageRef (regclient) ungültig (%s): %v", tag, err) - return - } - desc, err := rc.ManifestHead(ctx, refObj) - if err != nil { - log.Printf("Remote-Manifest nicht gefunden (%s): %v", tag, err) - return - } - remoteDigest := desc.GetDigest().String() - - // Logging - fmt.Printf("Image: %s\n Local Digest: %s\n Remote Digest: %s\n", tag, localDigest, remoteDigest) - var update float64 = 0 - if localDigest != remoteDigest { - fmt.Println(" -> Update verfügbar!") - update = 1 - } else { - fmt.Println(" -> Kein Update verfügbar.") - } - - labelImg, labelTag := tag, "latest" - if cp := strings.Split(tag, ":"); len(cp) == 2 { - labelImg, labelTag = cp[0], cp[1] - } - ch <- prometheus.MustNewConstMetric( - c.metric, prometheus.GaugeValue, - update, labelImg, labelTag, - ) - }(tag, img) + if len(img.RepoDigests) > 0 { + imageTagToDigest[tag] = extractDigest(img.RepoDigests[0]) + } else { + imageTagToDigest[tag] = img.ID + } } } + + var wg sync.WaitGroup + for _, ctr := range containers { + tag := ctr.Image + + imageRef, err := toRegistryImage(tag) + if err != nil { + log.Printf("ImageRef-Fehler bei %s: %v", tag, err) + continue + } + localDigest := imageTagToDigest[tag] + + wg.Add(1) + go func(ctr container.Summary, tag, localDigest, imageRef string) { + defer wg.Done() + refObj, err := ref.New(imageRef) + if err != nil { + log.Printf("ImageRef (regclient) ungültig (%s): %v", tag, err) + return + } + desc, err := rc.ManifestHead(ctx, refObj) + if err != nil { + log.Printf("Remote-Manifest nicht gefunden (%s): %v", tag, err) + return + } + remoteDigest := desc.GetDigest().String() + + fmt.Printf("Container: %s\n Image: %s\n Local Digest: %s\n Remote Digest: %s\n", ctr.Names[0], tag, localDigest, remoteDigest) + var update float64 = 0 + if localDigest != remoteDigest { + fmt.Println(" -> Update verfügbar!") + update = 1 + } else { + fmt.Println(" -> Kein Update verfügbar.") + } + // Labels container_name, image, tag + labelImg, labelTag := tag, "latest" + if cp := strings.Split(tag, ":"); len(cp) == 2 { + labelImg, labelTag = cp[0], cp[1] + } + ch <- prometheus.MustNewConstMetric( + c.metric, prometheus.GaugeValue, + update, ctr.Names[0], labelImg, labelTag, + ) + }(ctr, tag, localDigest, imageRef) + } wg.Wait() } func main() { - log.Println("Starte Docker-Image-Update Prometheus Exporter (Port 9788)...") + log.Println("Starte Prometheus Exporter für laufende Container-Images (Port 9788)...") exporter := newImageUpdateCollector() prometheus.MustRegister(exporter) http.Handle("/metrics", promhttp.Handler())