diff --git a/plugins/container/go-worker/pkg/container/containerd.go b/plugins/container/go-worker/pkg/container/containerd.go index e8a62aec..a5fa38d6 100644 --- a/plugins/container/go-worker/pkg/container/containerd.go +++ b/plugins/container/go-worker/pkg/container/containerd.go @@ -147,11 +147,7 @@ func (c *containerdEngine) ctrToInfo(namespacedContext context.Context, containe imageSize = image.Target().Size } } - imageRepoTag := strings.Split(info.Image, ":") - if len(imageRepoTag) == 2 { - imageRepo = imageRepoTag[0] - imageTag = imageRepoTag[1] - } + imageRepo, imageTag = parseImageRepoTag(info.Image) // Network related - TODO diff --git a/plugins/container/go-worker/pkg/container/cri.go b/plugins/container/go-worker/pkg/container/cri.go index e0dd26c2..5587bafc 100644 --- a/plugins/container/go-worker/pkg/container/cri.go +++ b/plugins/container/go-worker/pkg/container/cri.go @@ -322,27 +322,18 @@ func (c *criEngine) ctrToInfo(ctx context.Context, ctr *v1.ContainerStatus, podS } } - imageRepoTag := strings.Split(imageName, ":") - imageRepo = imageRepoTag[0] - if len(imageRepoTag) == 2 { - imageTag = imageRepoTag[1] - } + imageRepo, imageTag = parseImageRepoTag(imageName) if getTagFromImage { - imageRepoTag = strings.Split(ctr.GetImage().GetImage(), ":") - if len(imageRepoTag) == 2 { - imageTag = imageRepoTag[1] + _, tag := parseImageRepoTag(ctr.GetImage().GetImage()) + if tag != "" { + imageTag = tag imageName += ":" + imageTag } } imageStr := ctrInfo.getImage() - imageStrs := strings.Split(imageStr, ":") - if len(imageStrs) == 2 { - imageID = imageStrs[1] - } else { - imageID = imageStr - } + _, imageID = parseImageRepoTag(imageStr) if imageID == "" { imageID = ctr.GetImageId() } diff --git a/plugins/container/go-worker/pkg/container/docker.go b/plugins/container/go-worker/pkg/container/docker.go index c6415a60..44d1dc10 100644 --- a/plugins/container/go-worker/pkg/container/docker.go +++ b/plugins/container/go-worker/pkg/container/docker.go @@ -200,23 +200,23 @@ func (dc *dockerEngine) ctrToInfo(ctx context.Context, ctr container.InspectResp } for _, repoTag := range img.RepoTags { - repoTagsParts := strings.Split(repoTag, ":") - if len(repoTagsParts) != 2 { + repo, tag := parseImageRepoTag(repoTag) + if repo == "" || tag == "" { // malformed continue } if imageRepo == "" { - imageRepo = repoTagsParts[0] + imageRepo = repo } if strings.Contains(repoTag, imageRepo) { - imageTag = repoTagsParts[1] + imageTag = tag break } } imgName := ctr.Image - if !strings.Contains(imgName, "/") && strings.Contains(imgName, ":") { - imageID = strings.Split(imgName, ":")[1] + if !strings.Contains(imgName, "/") { + _, imageID = parseImageRepoTag(imgName) } labels := make(map[string]string) diff --git a/plugins/container/go-worker/pkg/container/engine.go b/plugins/container/go-worker/pkg/container/engine.go index de6473c2..cea83855 100644 --- a/plugins/container/go-worker/pkg/container/engine.go +++ b/plugins/container/go-worker/pkg/container/engine.go @@ -182,3 +182,41 @@ func parsePortBindingHostPort(port string) (uint16, error) { return uint16(convertedPort), nil } + +// parseImageRepoTag parses a container image string and returns the repository and tag. +// It correctly handles registry URLs with port numbers by only splitting on the last colon +// that appears after the last slash. If the reference includes a digest (via "@"), the +// digest portion is removed first, and then the tag is extracted from the remaining string. +// +// Examples: +// - "registry.example.com:5000/foo/bar:latest@sha256:digest" -> ("registry.example.com:5000/foo/bar", "latest") +// - "registry.example.com:5000/foo/bar@sha256:digest" -> ("registry.example.com:5000/foo/bar", "") +// - "registry.example.com:5000/foo/bar:latest" -> ("registry.example.com:5000/foo/bar", "latest") +// - "registry.example.com:5000/foo/bar" -> ("registry.example.com:5000/foo/bar", "") +// - "foo/bar:latest" -> ("foo/bar", "latest") +// - "foo:latest" -> ("foo", "latest") +func parseImageRepoTag(image string) (repo, tag string) { + if image == "" { + return "", "" + } + + // Remove digest portion (e.g., @sha256:...) if present + if at := strings.Index(image, "@"); at != -1 { + image = image[:at] + } + + // Find the last slash to separate the registry/path from the image name + lastSlash := strings.LastIndex(image, "/") + + // Find the last colon after the last slash (if any) + // This colon separates the tag from the repo + lastColon := strings.LastIndex(image, ":") + + // If there's no colon, or the colon appears before the last slash + // (meaning it's part of a registry port), then there's no tag + if lastColon == -1 || (lastSlash != -1 && lastColon < lastSlash) { + return image, "" + } + + return image[:lastColon], image[lastColon+1:] +} diff --git a/plugins/container/go-worker/pkg/container/engine_test.go b/plugins/container/go-worker/pkg/container/engine_test.go index 6ea14583..58686c53 100644 --- a/plugins/container/go-worker/pkg/container/engine_test.go +++ b/plugins/container/go-worker/pkg/container/engine_test.go @@ -140,3 +140,70 @@ func TestParsePortBindingHostPort(t *testing.T) { }) } } + +func TestParseImageRepoTag(t *testing.T) { + tCases := map[string]struct { + image string + expectedRepo string + expectedTag string + }{ + "Registry with port and tag": { + image: "registry.example.com:5000/foo/bar:latest", + expectedRepo: "registry.example.com:5000/foo/bar", + expectedTag: "latest", + }, + "Registry with port without tag": { + image: "registry.example.com:5000/foo/bar", + expectedRepo: "registry.example.com:5000/foo/bar", + expectedTag: "", + }, + "Simple image with tag": { + image: "foo/bar:latest", + expectedRepo: "foo/bar", + expectedTag: "latest", + }, + "Simple image without path with tag": { + image: "foo:latest", + expectedRepo: "foo", + expectedTag: "latest", + }, + "Simple image without tag": { + image: "foo/bar", + expectedRepo: "foo/bar", + expectedTag: "", + }, + "Empty string": { + image: "", + expectedRepo: "", + expectedTag: "", + }, + "Digest based image": { + image: "registry.example.com:5000/foo/bar@sha256:abc123", + expectedRepo: "registry.example.com:5000/foo/bar", + expectedTag: "", + }, + "Both tag and digest": { + image: "registry.example.com:5000/foo/bar:latest@sha256:abc123", + expectedRepo: "registry.example.com:5000/foo/bar", + expectedTag: "latest", + }, + "Multi-level path with registry port and tag": { + image: "registry.example.com:5000/org/project/image:v1.2.3", + expectedRepo: "registry.example.com:5000/org/project/image", + expectedTag: "v1.2.3", + }, + "Localhost with port and tag": { + image: "localhost:5000/myimage:latest", + expectedRepo: "localhost:5000/myimage", + expectedTag: "latest", + }, + } + + for name, tc := range tCases { + t.Run(name, func(t *testing.T) { + repo, tag := parseImageRepoTag(tc.image) + assert.Equal(t, tc.expectedRepo, repo) + assert.Equal(t, tc.expectedTag, tag) + }) + } +} diff --git a/plugins/container/go-worker/pkg/container/podman.go b/plugins/container/go-worker/pkg/container/podman.go index 283569df..f0014bc8 100644 --- a/plugins/container/go-worker/pkg/container/podman.go +++ b/plugins/container/go-worker/pkg/container/podman.go @@ -106,11 +106,7 @@ func (pc *podmanEngine) ctrToInfo(ctr *define.InspectContainerData) event.Info { imageRepo string imageTag string ) - imageRepoTag := strings.Split(ctr.ImageName, ":") - if len(imageRepoTag) == 2 { - imageRepo = imageRepoTag[0] - imageTag = imageRepoTag[1] - } + imageRepo, imageTag = parseImageRepoTag(ctr.ImageName) labels := make(map[string]string) var (