From 2b68874087bfee59dcb265f0b1f301808bbe3e2b Mon Sep 17 00:00:00 2001 From: Simon Aronsson Date: Sat, 21 Nov 2020 14:15:59 +0100 Subject: [PATCH] increase test coverage and fix some minor bugs --- pkg/registry/auth/auth.go | 18 ++++++-- pkg/registry/auth/auth_test.go | 52 +++++++++++++++++++++++ pkg/registry/manifest/manifest.go | 21 +++++++--- pkg/registry/manifest/manifest_test.go | 57 ++++++++++++++++++++++++++ 4 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 pkg/registry/auth/auth_test.go create mode 100644 pkg/registry/manifest/manifest_test.go diff --git a/pkg/registry/auth/auth.go b/pkg/registry/auth/auth.go index eec03ef..4aa1360 100644 --- a/pkg/registry/auth/auth.go +++ b/pkg/registry/auth/auth.go @@ -76,7 +76,11 @@ func GetChallengeRequest(url url2.URL) (*http.Request, error) { func GetBearerToken(ctx context.Context, challenge string, img string, err error, credentials *types.RegistryCredentials) (string, error) { log := logger.GetLogger(ctx) client := http.Client{} - authURL := GetAuthURL(challenge, img) + + authURL, err := GetAuthURL(challenge, img) + if err != nil { + return "", err + } var r *http.Request if r, err = http.NewRequest("GET", authURL.String(), nil); err != nil { @@ -107,8 +111,10 @@ func GetBearerToken(ctx context.Context, challenge string, img string, err error } // GetAuthURL from the instructions in the challenge -func GetAuthURL(challenge string, img string) *url2.URL { - raw := strings.TrimPrefix(challenge, "bearer") +func GetAuthURL(challenge string, img string) (*url2.URL, error) { + loweredChallenge := strings.ToLower(challenge) + raw := strings.TrimPrefix(loweredChallenge, "bearer") + pairs := strings.Split(raw, ",") values := make(map[string]string, 0) for _, pair := range pairs { @@ -119,6 +125,10 @@ func GetAuthURL(challenge string, img string) *url2.URL { values[key] = val } + if values["realm"] == "" || values["service"] == "" || values["scope"] == "" { + return nil, fmt.Errorf("challenge header did not include all values needed to construct an auth url") + } + authURL, _ := url2.Parse(fmt.Sprintf("%s", values["realm"])) q := authURL.Query() q.Add("service", values["service"]) @@ -127,7 +137,7 @@ func GetAuthURL(challenge string, img string) *url2.URL { q.Add("scope", scope) authURL.RawQuery = q.Encode() - return authURL + return authURL, nil } // GetChallengeURL creates a URL object based on the image info diff --git a/pkg/registry/auth/auth_test.go b/pkg/registry/auth/auth_test.go new file mode 100644 index 0000000..5698209 --- /dev/null +++ b/pkg/registry/auth/auth_test.go @@ -0,0 +1,52 @@ +package auth + +import ( + "net/url" + "testing" + . "github.com/onsi/gomega" + . "github.com/onsi/ginkgo" +) + +func TestAuth(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Registry Auth Suite") +} + +var _ = Describe("the auth module", func() { + When("getting an auth url", func() { + It("should create a valid auth url object based on the challenge header supplied", func() { + input := `bearer realm="https://ghcr.io/token",service="ghcr.io",scope="repository:user/image:pull"` + expected := &url.URL{ + Host: "ghcr.io", + Scheme: "https", + Path: "/token", + RawQuery: "scope=repository%3Acontainrrr%2Fwatchtower%3Apull&service=ghcr.io", + } + res, err := GetAuthURL(input, "containrrr/watchtower") + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(expected)) + }) + It("should create a valid auth url object based on the challenge header supplied", func() { + input := `bearer realm="https://ghcr.io/token",service="ghcr.io"` + res, err := GetAuthURL(input, "containrrr/watchtower") + Expect(err).To(HaveOccurred()) + Expect(res).To(BeNil()) + }) + }) + When("getting a challenge url", func() { + It("should create a valid challenge url object based on the image ref supplied", func() { + expected := url.URL{ Host: "ghcr.io", Scheme: "https", Path: "/v2/"} + Expect(GetChallengeURL("ghcr.io/containrrr/watchtower:latest")).To(Equal(expected)) + }) + It("should assume dockerhub if the image ref is not fully qualified", func() { + expected := url.URL{ Host: "index.docker.io", Scheme: "https", Path: "/v2/"} + Expect(GetChallengeURL("containrrr/watchtower:latest")).To(Equal(expected)) + }) + It("should convert legacy dockerhub hostnames to index.docker.io", func() { + expected := url.URL{ Host: "index.docker.io", Scheme: "https", Path: "/v2/"} + Expect(GetChallengeURL("docker.io/containrrr/watchtower:latest")).To(Equal(expected)) + Expect(GetChallengeURL("registry-1.docker.io/containrrr/watchtower:latest")).To(Equal(expected)) + }) + }) +}) + diff --git a/pkg/registry/manifest/manifest.go b/pkg/registry/manifest/manifest.go index 5a11caf..9a089f9 100644 --- a/pkg/registry/manifest/manifest.go +++ b/pkg/registry/manifest/manifest.go @@ -11,12 +11,9 @@ import ( // BuildManifestURL from raw image data func BuildManifestURL(image apiTypes.ImageInspect) (string, error) { - parts := strings.Split(image.RepoTags[0], ":") - img := parts[0] - tag := parts[1] + img, tag := extractImageAndTag(image) hostName, err := ref.ParseNormalizedNamed(img) - fmt.Println(hostName) if err != nil { return "", err } @@ -25,7 +22,7 @@ func BuildManifestURL(image apiTypes.ImageInspect) (string, error) { if err != nil { return "", err } - img = strings.TrimPrefix(img, host) + img = strings.TrimPrefix(img, fmt.Sprintf("%s/", host)) url := url2.URL{ Scheme: "https", Host: host, @@ -33,3 +30,17 @@ func BuildManifestURL(image apiTypes.ImageInspect) (string, error) { } return url.String(), nil } + +func extractImageAndTag(image apiTypes.ImageInspect) (string, string) { + var img string + var tag string + if strings.Contains(image.RepoTags[0], ":") { + parts := strings.Split(image.RepoTags[0], ":") + img = parts[0] + tag = parts[1] + } else { + img = image.RepoTags[0] + tag = "latest" + } + return img, tag +} diff --git a/pkg/registry/manifest/manifest_test.go b/pkg/registry/manifest/manifest_test.go new file mode 100644 index 0000000..1e34dd6 --- /dev/null +++ b/pkg/registry/manifest/manifest_test.go @@ -0,0 +1,57 @@ +package manifest_test + +import ( + "github.com/containrrr/watchtower/pkg/registry/manifest" + apiTypes "github.com/docker/docker/api/types" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "testing" +) + +func TestManifest(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Manifest Suite") +} + +var _ = Describe("the manifest module", func() { + + When("building a manifest url", 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/containrrr/watchtower:latest", + }, + } + + res, err := manifest.BuildManifestURL(imageInfo) + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(expected)) + }) + It("should assume dockerhub for non-qualified images", func() { + expected := "https://index.docker.io/v2/containrrr/watchtower/manifests/latest" + imageInfo := apiTypes.ImageInspect{ + RepoTags: []string { + "containrrr/watchtower:latest", + }, + } + + res, err := manifest.BuildManifestURL(imageInfo) + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(expected)) + }) + It("should assume latest for images that lack an explicit tag", func() { + expected := "https://index.docker.io/v2/containrrr/watchtower/manifests/latest" + imageInfo := apiTypes.ImageInspect{ + RepoTags: []string { + "containrrr/watchtower", + }, + } + + res, err := manifest.BuildManifestURL(imageInfo) + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(expected)) + }) + }) + +})