diff --git a/pkg/registry/manifest/manifest.go b/pkg/registry/manifest/manifest.go index facbb6c..2d5730c 100644 --- a/pkg/registry/manifest/manifest.go +++ b/pkg/registry/manifest/manifest.go @@ -1,42 +1,43 @@ package manifest import ( + "errors" "fmt" - "github.com/containrrr/watchtower/pkg/registry/auth" + url2 "net/url" + "github.com/containrrr/watchtower/pkg/registry/helpers" "github.com/containrrr/watchtower/pkg/types" ref "github.com/docker/distribution/reference" "github.com/sirupsen/logrus" - url2 "net/url" - "strings" ) // BuildManifestURL from raw image data func BuildManifestURL(container types.Container) (string, error) { - - normalizedName, err := ref.ParseNormalizedNamed(container.ImageName()) - if err != nil { + var normalizedTaggedRef ref.NamedTagged + if normalizedRef, err := ref.ParseDockerRef(container.ImageName()); err == nil { + var isTagged bool + normalizedTaggedRef, isTagged = normalizedRef.(ref.NamedTagged) + if !isTagged { + return "", errors.New("Parsed container image ref has no tag: " + normalizedRef.String()) + } + } else { return "", err } - host, err := helpers.NormalizeRegistry(normalizedName.String()) - img, tag := ExtractImageAndTag(strings.TrimPrefix(container.ImageName(), host+"/")) + host, err := helpers.NormalizeRegistry(normalizedTaggedRef.Name()) + img, tag := ExtractImageAndTag(normalizedTaggedRef) logrus.WithFields(logrus.Fields{ "image": img, "tag": tag, - "normalized": normalizedName, + "normalized": normalizedTaggedRef.Name(), "host": host, }).Debug("Parsing image ref") if err != nil { return "", err } - img = auth.GetScopeFromImageName(img, host) - if !strings.Contains(img, "/") { - img = "library/" + img - } url := url2.URL{ Scheme: "https", Host: host, @@ -45,23 +46,7 @@ func BuildManifestURL(container types.Container) (string, error) { return url.String(), nil } -// ExtractImageAndTag from a concatenated string -func ExtractImageAndTag(imageName string) (string, string) { - var img string - var tag string - - if strings.Contains(imageName, ":") { - parts := strings.Split(imageName, ":") - if len(parts) > 2 { - img = parts[0] - tag = strings.Join(parts[1:], ":") - } else { - img = parts[0] - tag = parts[1] - } - } else { - img = imageName - tag = "latest" - } - return img, tag +// ExtractImageAndTag from image reference +func ExtractImageAndTag(namedTaggedRef ref.NamedTagged) (string, string) { + return ref.Path(namedTaggedRef), namedTaggedRef.Tag() } diff --git a/pkg/registry/manifest/manifest_test.go b/pkg/registry/manifest/manifest_test.go index 95f196b..2c81323 100644 --- a/pkg/registry/manifest/manifest_test.go +++ b/pkg/registry/manifest/manifest_test.go @@ -1,13 +1,14 @@ package manifest_test import ( + "testing" + "time" + "github.com/containrrr/watchtower/internal/actions/mocks" "github.com/containrrr/watchtower/pkg/registry/manifest" apiTypes "github.com/docker/docker/api/types" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "testing" - "time" ) func TestManifest(t *testing.T) { @@ -16,60 +17,54 @@ func TestManifest(t *testing.T) { } var _ = Describe("the manifest module", func() { - mockId := "mock-id" - mockName := "mock-container" - mockCreated := time.Now() - - When("building a manifest url", func() { + Describe("BuildManifestURL", func() { It("should return a valid url given a fully qualified image", func() { expected := "https://ghcr.io/v2/containrrr/watchtower/manifests/latest" - imageInfo := apiTypes.ImageInspect{ - RepoTags: []string{ - "ghcr.io/k6io/operator:latest", - }, - } - mock := mocks.CreateMockContainerWithImageInfo(mockId, mockName, "ghcr.io/containrrr/watchtower:latest", mockCreated, imageInfo) - res, err := manifest.BuildManifestURL(mock) + + URL, err := buildMockContainerManifestURL("ghcr.io/containrrr/watchtower:latest") Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal(expected)) + Expect(URL).To(Equal(expected)) }) - It("should assume dockerhub for non-qualified images", func() { + It("should assume Docker Hub for non-qualified images", func() { expected := "https://index.docker.io/v2/containrrr/watchtower/manifests/latest" - imageInfo := apiTypes.ImageInspect{ - RepoTags: []string{ - "containrrr/watchtower:latest", - }, - } - mock := mocks.CreateMockContainerWithImageInfo(mockId, mockName, "containrrr/watchtower:latest", mockCreated, imageInfo) - res, err := manifest.BuildManifestURL(mock) + URL, err := buildMockContainerManifestURL("containrrr/watchtower:latest") Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal(expected)) + Expect(URL).To(Equal(expected)) }) - It("should assume latest for images that lack an explicit tag", func() { + It("should assume latest for image refs without an explicit tag", func() { expected := "https://index.docker.io/v2/containrrr/watchtower/manifests/latest" - imageInfo := apiTypes.ImageInspect{ - RepoTags: []string{ - "containrrr/watchtower", - }, - } - - mock := mocks.CreateMockContainerWithImageInfo(mockId, mockName, "containrrr/watchtower", mockCreated, imageInfo) - - res, err := manifest.BuildManifestURL(mock) + URL, err := buildMockContainerManifestURL("containrrr/watchtower") Expect(err).NotTo(HaveOccurred()) - Expect(res).To(Equal(expected)) + Expect(URL).To(Equal(expected)) }) - It("should combine the tag name and digest pinning into one digest, given multiple colons", func() { - in := "containrrr/watchtower:latest@sha256:daf7034c5c89775afe3008393ae033529913548243b84926931d7c84398ecda7" - image, tag := "containrrr/watchtower", "latest@sha256:daf7034c5c89775afe3008393ae033529913548243b84926931d7c84398ecda7" + It("should not prepend library/ for single-part container names in registries other than Docker Hub", func() { + expected := "https://docker-registry.domain/v2/imagename/manifests/latest" - imageOut, tagOut := manifest.ExtractImageAndTag(in) - - Expect(imageOut).To(Equal(image)) - Expect(tagOut).To(Equal(tag)) + URL, err := buildMockContainerManifestURL("docker-registry.domain/imagename:latest") + Expect(err).NotTo(HaveOccurred()) + Expect(URL).To(Equal(expected)) + }) + It("should throw an error on pinned images", func() { + imageRef := "docker-registry.domain/imagename@sha256:daf7034c5c89775afe3008393ae033529913548243b84926931d7c84398ecda7" + URL, err := buildMockContainerManifestURL(imageRef) + Expect(err).To(HaveOccurred()) + Expect(URL).To(BeEmpty()) }) }) - }) + +func buildMockContainerManifestURL(imageRef string) (string, error) { + imageInfo := apiTypes.ImageInspect{ + RepoTags: []string{ + imageRef, + }, + } + mockID := "mock-id" + mockName := "mock-container" + mockCreated := time.Now() + mock := mocks.CreateMockContainerWithImageInfo(mockID, mockName, imageRef, mockCreated, imageInfo) + + return manifest.BuildManifestURL(mock) +}