diff --git a/internal/provider/common.go b/internal/provider/common.go index 948b9ad9..7197ef18 100644 --- a/internal/provider/common.go +++ b/internal/provider/common.go @@ -6,12 +6,10 @@ import ( "strings" "github.com/crazy-max/diun/v4/internal/model" - "github.com/rs/zerolog/log" ) -// ValidateContainerImage returns a standard image through Docker labels -func ValidateContainerImage(image string, labels map[string]string, watchByDef bool) (img model.Image, err error) { - log.Debug().Msgf("Validating %s", image) +// ValidateImage returns a standard image through Docker labels +func ValidateImage(image string, labels map[string]string, watchByDef bool) (img model.Image, err error) { if i := strings.Index(image, "@sha256:"); i > 0 { image = image[:i] } diff --git a/internal/provider/docker/container.go b/internal/provider/docker/container.go index b869a867..0cc90b18 100644 --- a/internal/provider/docker/container.go +++ b/internal/provider/docker/container.go @@ -36,28 +36,73 @@ func (c *Client) listContainerImage() []model.Image { var list []model.Image for _, ctn := range ctns { - imageRaw, err := cli.RawImage(ctn.Image) + imageName := ctn.Image + imageRaw, err := cli.ImageInspectWithRaw(imageName) if err != nil { - c.logger.Error().Err(err).Msgf("Cannot inspect image from container %s", ctn.ID) - continue - } - if local := cli.IsLocalImage(imageRaw); local { - c.logger.Debug().Msgf("Skip locally built image from container %s", ctn.ID) - continue - } - if dangling := cli.IsDanglingImage(imageRaw); dangling { - c.logger.Debug().Msgf("Skip dangling image from container %s", ctn.ID) + c.logger.Error().Err(err). + Str("ctn_id", ctn.ID). + Str("ctn_image", imageName). + Msg("Cannot inspect image") continue } - image, err := provider.ValidateContainerImage(ctn.Image, ctn.Labels, *c.config.WatchByDefault) - if err != nil { - c.logger.Error().Err(err).Msgf("Cannot get image from container %s", ctn.ID) - continue - } else if reflect.DeepEqual(image, model.Image{}) { - c.logger.Debug().Msgf("Watch disabled for container %s", ctn.ID) + if local := cli.IsLocalImage(imageRaw); local { + c.logger.Debug(). + Str("ctn_id", ctn.ID). + Str("ctn_image", imageName). + Msg("Skip locally built image") continue } + + if dangling := cli.IsDanglingImage(imageRaw); dangling { + c.logger.Debug(). + Str("ctn_id", ctn.ID). + Str("ctn_image", imageName). + Msg("Skip dangling image") + continue + } + + if cli.IsDigest(imageName) { + if len(imageRaw.RepoDigests) > 0 { + c.logger.Debug(). + Str("ctn_id", ctn.ID). + Str("ctn_image", imageName). + Strs("img_repodigests", imageRaw.RepoDigests). + Msg("Using first image repo digest available as image name") + imageName = imageRaw.RepoDigests[0] + } else { + c.logger.Debug(). + Str("ctn_id", ctn.ID). + Str("ctn_image", imageName). + Strs("img_repodigests", imageRaw.RepoDigests). + Msg("Skip unknown image digest ref") + continue + } + } + + c.logger.Debug(). + Str("ctn_id", ctn.ID). + Str("ctn_image", imageName). + Interface("ctn_labels", ctn.Labels). + Msg("Validate image") + image, err := provider.ValidateImage(imageName, ctn.Labels, *c.config.WatchByDefault) + + if err != nil { + c.logger.Error().Err(err). + Str("ctn_id", ctn.ID). + Str("ctn_image", imageName). + Interface("ctn_labels", ctn.Labels). + Msg("Invalid image") + continue + } else if reflect.DeepEqual(image, model.Image{}) { + c.logger.Debug(). + Str("ctn_id", ctn.ID). + Str("ctn_image", imageName). + Interface("ctn_labels", ctn.Labels). + Msg("Watch disabled") + continue + } + list = append(list, image) } diff --git a/internal/provider/kubernetes/pod.go b/internal/provider/kubernetes/pod.go index 93a47dca..f71df1b7 100644 --- a/internal/provider/kubernetes/pod.go +++ b/internal/provider/kubernetes/pod.go @@ -32,14 +32,32 @@ func (c *Client) listPodImage() []model.Image { var list []model.Image for _, pod := range pods { for _, ctn := range pod.Spec.Containers { - image, err := provider.ValidateContainerImage(ctn.Image, pod.Annotations, *c.config.WatchByDefault) + c.logger.Debug(). + Str("pod_name", pod.Name). + Interface("pod_annot", pod.Annotations). + Str("ctn_name", ctn.Name). + Str("ctn_image", ctn.Image). + Msg("Validate image") + + image, err := provider.ValidateImage(ctn.Image, pod.Annotations, *c.config.WatchByDefault) if err != nil { - c.logger.Error().Err(err).Msgf("Cannot get image from container %s (pod %s)", ctn.Name, pod.Name) + c.logger.Error().Err(err). + Str("pod_name", pod.Name). + Interface("pod_annot", pod.Annotations). + Str("ctn_name", ctn.Name). + Str("ctn_image", ctn.Image). + Msg("Invalid image") continue } else if reflect.DeepEqual(image, model.Image{}) { - c.logger.Debug().Msgf("Watch disabled for container %s (pod %s)", ctn.Name, pod.Name) + c.logger.Debug(). + Str("pod_name", pod.Name). + Interface("pod_annot", pod.Annotations). + Str("ctn_name", ctn.Name). + Str("ctn_image", ctn.Image). + Msg("Watch disabled") continue } + list = append(list, image) } } diff --git a/internal/provider/swarm/service.go b/internal/provider/swarm/service.go index 6b465b5f..90264407 100644 --- a/internal/provider/swarm/service.go +++ b/internal/provider/swarm/service.go @@ -29,25 +29,73 @@ func (c *Client) listServiceImage() []model.Image { var list []model.Image for _, svc := range svcs { - if imageRaw, err := cli.RawImage(svc.Spec.TaskTemplate.ContainerSpec.Image); err == nil { - if local := cli.IsLocalImage(imageRaw); local { - c.logger.Debug().Msgf("Skip locally built image for service %s", svc.Spec.Name) - continue - } - if dangling := cli.IsDanglingImage(imageRaw); dangling { - c.logger.Debug().Msgf("Skip dangling image for service %s", svc.Spec.Name) + imageName := svc.Spec.TaskTemplate.ContainerSpec.Image + imageRaw, err := cli.ImageInspectWithRaw(svc.Spec.TaskTemplate.ContainerSpec.Image) + if err != nil { + c.logger.Error().Err(err). + Str("svc_name", svc.Spec.Name). + Str("ctn_image", imageName). + Msg("Cannot inspect image") + continue + } + + if local := cli.IsLocalImage(imageRaw); local { + c.logger.Debug(). + Str("svc_name", svc.Spec.Name). + Str("ctn_image", imageName). + Msg("Skip locally built image") + continue + } + + if dangling := cli.IsDanglingImage(imageRaw); dangling { + c.logger.Debug(). + Str("svc_name", svc.Spec.Name). + Str("ctn_image", imageName). + Msg("Skip dangling image") + continue + } + + if cli.IsDigest(imageName) { + if len(imageRaw.RepoDigests) > 0 { + c.logger.Debug(). + Str("svc_name", svc.Spec.Name). + Str("ctn_image", imageName). + Strs("img_repodigests", imageRaw.RepoDigests). + Msg("Using first image repo digest available as image name") + imageName = imageRaw.RepoDigests[0] + } else { + c.logger.Debug(). + Str("svc_name", svc.Spec.Name). + Str("ctn_image", imageName). + Strs("img_repodigests", imageRaw.RepoDigests). + Msg("Skip unknown image digest ref") continue } } - image, err := provider.ValidateContainerImage(svc.Spec.TaskTemplate.ContainerSpec.Image, svc.Spec.Labels, *c.config.WatchByDefault) + c.logger.Debug(). + Str("svc_name", svc.Spec.Name). + Interface("svc_labels", svc.Spec.Labels). + Str("ctn_image", imageName). + Msg("Validate image") + + image, err := provider.ValidateImage(imageName, svc.Spec.Labels, *c.config.WatchByDefault) if err != nil { - c.logger.Error().Err(err).Msgf("Cannot get image from service %s", svc.Spec.Name) + c.logger.Error().Err(err). + Str("svc_name", svc.Spec.Name). + Interface("svc_labels", svc.Spec.Labels). + Str("ctn_image", svc.Spec.TaskTemplate.ContainerSpec.Image). + Msg("Invalid image") continue } else if reflect.DeepEqual(image, model.Image{}) { - c.logger.Debug().Msgf("Watch disabled for service %s", svc.Spec.Name) + c.logger.Debug(). + Str("svc_name", svc.Spec.Name). + Interface("svc_labels", svc.Spec.Labels). + Str("ctn_image", svc.Spec.TaskTemplate.ContainerSpec.Image). + Msg("Watch disabled") continue } + list = append(list, image) } diff --git a/pkg/docker/image.go b/pkg/docker/image.go index 40351150..84b8e686 100644 --- a/pkg/docker/image.go +++ b/pkg/docker/image.go @@ -1,10 +1,24 @@ package docker -import "github.com/docker/docker/api/types" +import ( + "regexp" -// RawImage returns the image information and its raw representation -func (c *Client) RawImage(image string) (types.ImageInspect, error) { - imageRaw, _, err := c.API.ImageInspectWithRaw(c.ctx, image) + "github.com/docker/docker/api/types" +) + +// ContainerInspect returns the container information. +func (c *Client) ContainerInspect(containerID string) (types.ContainerJSON, error) { + return c.API.ContainerInspect(c.ctx, containerID) +} + +// IsDigest determines image it looks like a digest based image reference. +func (c *Client) IsDigest(imageID string) bool { + return regexp.MustCompile(`^(@|sha256:|@sha256:)([0-9a-f]{64})$`).MatchString(imageID) +} + +// ImageInspectWithRaw returns the image information and its raw representation. +func (c *Client) ImageInspectWithRaw(imageID string) (types.ImageInspect, error) { + imageRaw, _, err := c.API.ImageInspectWithRaw(c.ctx, imageID) return imageRaw, err }