image-checker/main.go

130 lines
3.5 KiB
Go
Raw Normal View History

2025-07-17 02:35:36 +02:00
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"regexp"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/client"
)
// Zerlegt einen Image-Namen in Registry, Repository und Tag
func parseImageName(imageName string) (registry, repo, tag string) {
defaultRegistry := "registry-1.docker.io"
tag = "latest"
r := regexp.MustCompile(`^(?:(?P<registry>[^/]+)/)?(?P<repo>[^:]+)(?::(?P<tag>.+))?$`)
match := r.FindStringSubmatch(imageName)
if len(match) == 0 {
return defaultRegistry, imageName, tag
}
index := r.SubexpIndex("registry")
if match[index] != "" {
registry = match[index]
} else {
registry = defaultRegistry
}
index = r.SubexpIndex("repo")
repo = match[index]
index = r.SubexpIndex("tag")
if match[index] != "" {
tag = match[index]
}
return
}
// Holt Digest aus Registry (funktioniert für öffentliche OCI-kompatible Registries)
func getRemoteDigest(registry, repo, tag string) (string, error) {
manifestURL := fmt.Sprintf("https://%s/v2/%s/manifests/%s", registry, repo, tag)
req, err := http.NewRequest("GET", manifestURL, nil)
if err != nil {
return "", err
}
req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("%s [%d]: %s", manifestURL, resp.StatusCode, body)
}
digest := resp.Header.Get("Docker-Content-Digest")
if digest == "" {
var manifest map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&manifest); err == nil {
if config, ok := manifest["config"].(map[string]interface{}); ok {
if dgst, ok := config["digest"].(string); ok {
return dgst, nil
}
}
}
return "", fmt.Errorf("Kein Digest in Registry-Manifest gefunden")
}
return digest, nil
}
// Gibt den Digest zu einem Image-Namen zurück (verwendet image.ListOptions)
func getLocalDigestOfImage(ctx context.Context, apiClient *client.Client, imageName string) (string, error) {
images, err := apiClient.ImageList(ctx, image.ListOptions{All: true})
if err != nil {
return "", err
}
for _, img := range images {
for _, t := range img.RepoTags {
if t == imageName {
if len(img.RepoDigests) > 0 {
return img.RepoDigests[0], nil
}
return img.ID, nil
}
}
}
return "", fmt.Errorf("Image %s nicht lokal gefunden", imageName)
}
func main() {
apiClient, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
panic(err)
}
defer apiClient.Close()
ctx := context.Background()
containers, err := apiClient.ContainerList(ctx, container.ListOptions{All: true})
if err != nil {
panic(err)
}
for _, ctr := range containers {
fmt.Printf("%s %s (status: %s)\n", ctr.ID, ctr.Image, ctr.Status)
registry, repo, tag := parseImageName(ctr.Image)
fmt.Printf(" Registry: %s\n Repo: %s\n Tag: %s\n", registry, repo, tag)
localDigest, err := getLocalDigestOfImage(ctx, apiClient, ctr.Image)
if err != nil {
fmt.Printf(" Fehler beim lokalen Digest: %v\n", err)
continue
}
remoteDigest, err := getRemoteDigest(registry, repo, tag)
if err != nil {
fmt.Printf(" Fehler beim Registry-Digest: %v\n", err)
continue
}
fmt.Println(" Local Digest: ", localDigest)
fmt.Println(" Remote Digest:", remoteDigest)
if localDigest == remoteDigest {
fmt.Println(" -> Image ist aktuell")
} else {
fmt.Println(" -> Update verfügbar!")
}
}
}