fix(manifest): use imported docker image ref manipulations

Closes #1525
This commit is contained in:
Reinier van der Leer 2023-01-22 16:24:23 +01:00
parent c16ac967c5
commit f0e075c390
No known key found for this signature in database
GPG key ID: DBC4942A5C29D7FA
2 changed files with 55 additions and 75 deletions

View file

@ -1,42 +1,43 @@
package manifest package manifest
import ( import (
"errors"
"fmt" "fmt"
"github.com/containrrr/watchtower/pkg/registry/auth" url2 "net/url"
"github.com/containrrr/watchtower/pkg/registry/helpers" "github.com/containrrr/watchtower/pkg/registry/helpers"
"github.com/containrrr/watchtower/pkg/types" "github.com/containrrr/watchtower/pkg/types"
ref "github.com/docker/distribution/reference" ref "github.com/docker/distribution/reference"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
url2 "net/url"
"strings"
) )
// BuildManifestURL from raw image data // BuildManifestURL from raw image data
func BuildManifestURL(container types.Container) (string, error) { func BuildManifestURL(container types.Container) (string, error) {
var normalizedTaggedRef ref.NamedTagged
normalizedName, err := ref.ParseNormalizedNamed(container.ImageName()) if normalizedRef, err := ref.ParseDockerRef(container.ImageName()); err == nil {
if 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 return "", err
} }
host, err := helpers.NormalizeRegistry(normalizedName.String()) host, err := helpers.NormalizeRegistry(normalizedTaggedRef.Name())
img, tag := ExtractImageAndTag(strings.TrimPrefix(container.ImageName(), host+"/")) img, tag := ExtractImageAndTag(normalizedTaggedRef)
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"image": img, "image": img,
"tag": tag, "tag": tag,
"normalized": normalizedName, "normalized": normalizedTaggedRef.Name(),
"host": host, "host": host,
}).Debug("Parsing image ref") }).Debug("Parsing image ref")
if err != nil { if err != nil {
return "", err return "", err
} }
img = auth.GetScopeFromImageName(img, host)
if !strings.Contains(img, "/") {
img = "library/" + img
}
url := url2.URL{ url := url2.URL{
Scheme: "https", Scheme: "https",
Host: host, Host: host,
@ -45,23 +46,7 @@ func BuildManifestURL(container types.Container) (string, error) {
return url.String(), nil return url.String(), nil
} }
// ExtractImageAndTag from a concatenated string // ExtractImageAndTag from image reference
func ExtractImageAndTag(imageName string) (string, string) { func ExtractImageAndTag(namedTaggedRef ref.NamedTagged) (string, string) {
var img string return ref.Path(namedTaggedRef), namedTaggedRef.Tag()
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
} }

View file

@ -1,13 +1,14 @@
package manifest_test package manifest_test
import ( import (
"testing"
"time"
"github.com/containrrr/watchtower/internal/actions/mocks" "github.com/containrrr/watchtower/internal/actions/mocks"
"github.com/containrrr/watchtower/pkg/registry/manifest" "github.com/containrrr/watchtower/pkg/registry/manifest"
apiTypes "github.com/docker/docker/api/types" apiTypes "github.com/docker/docker/api/types"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"testing"
"time"
) )
func TestManifest(t *testing.T) { func TestManifest(t *testing.T) {
@ -16,60 +17,54 @@ func TestManifest(t *testing.T) {
} }
var _ = Describe("the manifest module", func() { var _ = Describe("the manifest module", func() {
mockId := "mock-id" Describe("BuildManifestURL", func() {
mockName := "mock-container"
mockCreated := time.Now()
When("building a manifest url", func() {
It("should return a valid url given a fully qualified image", func() { It("should return a valid url given a fully qualified image", func() {
expected := "https://ghcr.io/v2/containrrr/watchtower/manifests/latest" expected := "https://ghcr.io/v2/containrrr/watchtower/manifests/latest"
imageInfo := apiTypes.ImageInspect{
RepoTags: []string{ URL, err := buildMockContainerManifestURL("ghcr.io/containrrr/watchtower:latest")
"ghcr.io/k6io/operator:latest",
},
}
mock := mocks.CreateMockContainerWithImageInfo(mockId, mockName, "ghcr.io/containrrr/watchtower:latest", mockCreated, imageInfo)
res, err := manifest.BuildManifestURL(mock)
Expect(err).NotTo(HaveOccurred()) 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" 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) URL, err := buildMockContainerManifestURL("containrrr/watchtower:latest")
res, err := manifest.BuildManifestURL(mock)
Expect(err).NotTo(HaveOccurred()) 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" expected := "https://index.docker.io/v2/containrrr/watchtower/manifests/latest"
imageInfo := apiTypes.ImageInspect{
RepoTags: []string{ URL, err := buildMockContainerManifestURL("containrrr/watchtower")
"containrrr/watchtower",
},
}
mock := mocks.CreateMockContainerWithImageInfo(mockId, mockName, "containrrr/watchtower", mockCreated, imageInfo)
res, err := manifest.BuildManifestURL(mock)
Expect(err).NotTo(HaveOccurred()) 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() { It("should not prepend library/ for single-part container names in registries other than Docker Hub", func() {
in := "containrrr/watchtower:latest@sha256:daf7034c5c89775afe3008393ae033529913548243b84926931d7c84398ecda7" expected := "https://docker-registry.domain/v2/imagename/manifests/latest"
image, tag := "containrrr/watchtower", "latest@sha256:daf7034c5c89775afe3008393ae033529913548243b84926931d7c84398ecda7"
imageOut, tagOut := manifest.ExtractImageAndTag(in) URL, err := buildMockContainerManifestURL("docker-registry.domain/imagename:latest")
Expect(err).NotTo(HaveOccurred())
Expect(imageOut).To(Equal(image)) Expect(URL).To(Equal(expected))
Expect(tagOut).To(Equal(tag)) })
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)
}