diff --git a/check_test.go b/check_test.go index 94c8a29..b13eeca 100644 --- a/check_test.go +++ b/check_test.go @@ -3,6 +3,7 @@ package resource_test import ( "bytes" "encoding/json" + "encoding/pem" "fmt" "net/http" "os/exec" @@ -20,6 +21,8 @@ import ( ) var _ = Describe("Check", func() { + var actualErr error + var req struct { Source resource.Source Version *resource.Version @@ -47,11 +50,11 @@ var _ = Describe("Check", func() { cmd.Stdout = outBuf cmd.Stderr = GinkgoWriter - err = cmd.Run() - Expect(err).ToNot(HaveOccurred()) - - err = json.Unmarshal(outBuf.Bytes(), &res) - Expect(err).ToNot(HaveOccurred()) + actualErr = cmd.Run() + if actualErr == nil { + err = json.Unmarshal(outBuf.Bytes(), &res) + Expect(err).ToNot(HaveOccurred()) + } } Describe("tracking a single tag", func() { @@ -68,6 +71,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_STATIC_DIGEST}, })) @@ -89,6 +94,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: PRIVATE_LATEST_STATIC_DIGEST}, })) @@ -138,12 +145,69 @@ var _ = Describe("Check", func() { }) It("falls back on fetching the manifest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_FAKE_DIGEST}, })) }) }) + Context("using a registry with self-signed certificate", func() { + var registry *ghttp.Server + + BeforeEach(func() { + registry = ghttp.NewTLSServer() + + registry.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/v2/"), + ghttp.RespondWith(http.StatusOK, `welcome to zombocom`), + ), + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/v2/"), + ghttp.RespondWith(http.StatusOK, `welcome to zombocom`), + ), + ghttp.CombineHandlers( + ghttp.VerifyRequest("HEAD", "/v2/some/fake-image/manifests/latest"), + ghttp.RespondWith(http.StatusOK, ``, LATEST_FAKE_HEADERS), + ), + ) + + req.Source.Repository = registry.Addr() + "/some/fake-image" + }) + + AfterEach(func() { + registry.Close() + }) + + When("the certificate is provided in 'source'", func() { + BeforeEach(func() { + certPem := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: registry.HTTPTestServer.Certificate().Raw, + }) + Expect(certPem).ToNot(BeEmpty()) + + req.Source.DomainCerts = []string{string(certPem)} + }) + + It("it checks and returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + + Expect(res).To(Equal([]resource.Version{ + {Tag: "latest", Digest: LATEST_FAKE_DIGEST}, + })) + }) + }) + + When("the certificate is missing in 'source'", func() { + It("exits non-zero and returns an error", func() { + Expect(actualErr).To(HaveOccurred()) + }) + }) + }) + Context("against a mirror", func() { var mirror *ghttp.Server @@ -187,6 +251,8 @@ var _ = Describe("Check", func() { }) It("it checks and returns the current digest using the registry declared in the repository and not using the mirror", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_FAKE_DIGEST}, })) @@ -216,6 +282,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_FAKE_DIGEST}, })) @@ -244,6 +312,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "1.32.0", Digest: latestDigest(req.Source.Name())}, })) @@ -266,6 +336,8 @@ var _ = Describe("Check", func() { }) It("returns the given digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_STATIC_DIGEST}, })) @@ -277,6 +349,8 @@ var _ = Describe("Check", func() { }) It("includes the tag in the response version", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_STATIC_DIGEST}, })) @@ -304,6 +378,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: PRIVATE_LATEST_STATIC_DIGEST}, })) @@ -351,6 +427,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ *req.Version, })) @@ -384,6 +462,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ *req.Version, })) @@ -407,6 +487,8 @@ var _ = Describe("Check", func() { }) It("returns the previous digest and the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: OLDER_STATIC_DIGEST}, {Tag: "latest", Digest: LATEST_STATIC_DIGEST}, @@ -435,6 +517,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: PRIVATE_OLDER_STATIC_DIGEST}, {Tag: "latest", Digest: PRIVATE_LATEST_STATIC_DIGEST}, @@ -488,6 +572,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: OLDER_FAKE_DIGEST}, {Tag: "latest", Digest: LATEST_FAKE_DIGEST}, @@ -520,6 +606,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "1.32.0", Digest: OLDER_LIBRARY_DIGEST}, {Tag: "1.32.0", Digest: latestDigest(req.Source.Name())}, @@ -543,6 +631,8 @@ var _ = Describe("Check", func() { }) It("returns only the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_STATIC_DIGEST}, })) @@ -564,6 +654,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: PRIVATE_LATEST_STATIC_DIGEST}, })) @@ -614,6 +706,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "latest", Digest: LATEST_FAKE_DIGEST}, })) @@ -642,6 +736,8 @@ var _ = Describe("Check", func() { }) It("returns the current digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{ {Tag: "1.32.0", Digest: latestDigest(req.Source.Name())}, })) @@ -660,6 +756,8 @@ var _ = Describe("Check", func() { }) It("returns empty digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{})) }) @@ -679,6 +777,8 @@ var _ = Describe("Check", func() { }) It("returns empty digest", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res).To(Equal([]resource.Version{})) }) }) diff --git a/in_test.go b/in_test.go index bbdf94f..43ea2be 100644 --- a/in_test.go +++ b/in_test.go @@ -3,6 +3,7 @@ package resource_test import ( "bytes" "encoding/json" + "encoding/pem" "io/ioutil" "net/http" "os" @@ -23,7 +24,10 @@ import ( ) var _ = Describe("In", func() { - var destDir string + var ( + actualErr error + destDir string + ) var req struct { Source resource.Source `json:"source"` @@ -70,11 +74,11 @@ var _ = Describe("In", func() { cmd.Stdout = outBuf cmd.Stderr = GinkgoWriter - err = cmd.Run() - Expect(err).ToNot(HaveOccurred()) - - err = json.Unmarshal(outBuf.Bytes(), &res) - Expect(err).ToNot(HaveOccurred()) + actualErr = cmd.Run() + if actualErr == nil { + err = json.Unmarshal(outBuf.Bytes(), &res) + Expect(err).ToNot(HaveOccurred()) + } }) Describe("image metadata", func() { @@ -87,6 +91,8 @@ var _ = Describe("In", func() { }) It("captures the env and user", func() { + Expect(actualErr).ToNot(HaveOccurred()) + var meta struct { User string `json:"user"` Env []string `json:"env"` @@ -116,6 +122,8 @@ var _ = Describe("In", func() { }) It("returns metadata", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res.Version).To(Equal(req.Version)) Expect(res.Metadata).To(Equal([]resource.MetadataField{ { @@ -148,11 +156,15 @@ var _ = Describe("In", func() { }) It("keeps file permissions and file modified times", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(stat.Mode()).To(Equal(os.FileMode(0603))) Expect(stat.ModTime()).To(BeTemporally("==", time.Date(1991, 06, 03, 05, 30, 30, 0, time.UTC))) }) It("keeps file ownership", func() { + Expect(actualErr).ToNot(HaveOccurred()) + if os.Geteuid() != 0 { Skip("Must be run as root to validate file ownership") } @@ -174,6 +186,8 @@ var _ = Describe("In", func() { }) It("does not restore files that were removed in later layers", func() { + Expect(actualErr).ToNot(HaveOccurred()) + infos, err := ioutil.ReadDir(rootfsPath("top-dir-1")) Expect(err).ToNot(HaveOccurred()) Expect(infos).To(HaveLen(2)) @@ -220,6 +234,8 @@ var _ = Describe("In", func() { }) It("works", func() { + Expect(actualErr).ToNot(HaveOccurred()) + lstat, err := os.Lstat(rootfsPath("usr", "libexec", "git-core", "git")) Expect(err).ToNot(HaveOccurred()) Expect(lstat.Mode() & os.ModeSymlink).To(BeZero()) @@ -240,6 +256,8 @@ var _ = Describe("In", func() { }) It("removes the symlink and writes to a new file rather than trying to open and write to it (thereby overwriting its target)", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(cat(rootfsPath("a"))).To(Equal("symlinked\n")) Expect(cat(rootfsPath("b"))).To(Equal("replaced\n")) }) @@ -266,6 +284,8 @@ var _ = Describe("In", func() { }) It("works", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(cat(rootfsPath("Dockerfile"))).To(ContainSubstring("hello!")) }) }) @@ -282,6 +302,8 @@ var _ = Describe("In", func() { }) It("saves the tagged image as image.tar instead of saving the rootfs", func() { + Expect(actualErr).ToNot(HaveOccurred()) + _, err := os.Stat(filepath.Join(destDir, "rootfs")) Expect(os.IsNotExist(err)).To(BeTrue()) @@ -313,6 +335,8 @@ var _ = Describe("In", func() { }) It("saves the digest to a file", func() { + Expect(actualErr).ToNot(HaveOccurred()) + digest, err := ioutil.ReadFile(filepath.Join(destDir, "digest")) Expect(err).ToNot(HaveOccurred()) Expect(string(digest)).To(Equal(req.Version.Digest)) @@ -329,6 +353,8 @@ var _ = Describe("In", func() { }) It("saves the tag to a file", func() { + Expect(actualErr).ToNot(HaveOccurred()) + tag, err := ioutil.ReadFile(filepath.Join(destDir, "tag")) Expect(err).ToNot(HaveOccurred()) Expect(string(tag)).To(Equal("tagged")) @@ -343,6 +369,8 @@ var _ = Describe("In", func() { }) It("saves the repository string to a file", func() { + Expect(actualErr).ToNot(HaveOccurred()) + repository, err := ioutil.ReadFile(filepath.Join(destDir, "repository")) Expect(err).ToNot(HaveOccurred()) Expect(string(repository)).To(Equal("concourse/test-image-static")) @@ -358,6 +386,8 @@ var _ = Describe("In", func() { }) It("does not download the image", func() { + Expect(actualErr).ToNot(HaveOccurred()) + _, err := os.Stat(filepath.Join(destDir, "rootfs")) Expect(os.IsNotExist(err)).To(BeTrue()) @@ -369,6 +399,8 @@ var _ = Describe("In", func() { }) It("saves the tag and digest files", func() { + Expect(actualErr).ToNot(HaveOccurred()) + digest, err := ioutil.ReadFile(filepath.Join(destDir, "digest")) Expect(err).ToNot(HaveOccurred()) Expect(string(digest)).To(Equal(LATEST_STATIC_DIGEST)) @@ -480,10 +512,87 @@ var _ = Describe("In", func() { }) It("retries", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res.Version).To(Equal(req.Version)) }) }) + Describe("using a registry with self-signed certificate", func() { + var registry *ghttp.Server + + BeforeEach(func() { + registry = ghttp.NewTLSServer() + + fakeImage := empty.Image + + digest, err := fakeImage.Digest() + Expect(err).ToNot(HaveOccurred()) + + manifest, err := fakeImage.RawManifest() + Expect(err).ToNot(HaveOccurred()) + + config, err := fakeImage.RawConfigFile() + Expect(err).ToNot(HaveOccurred()) + + configDigest, err := fakeImage.ConfigName() + Expect(err).ToNot(HaveOccurred()) + + registry.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/v2/"), + ghttp.RespondWith(http.StatusOK, `welcome to zombocom`), + ), + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/v2/"), + ghttp.RespondWith(http.StatusOK, `welcome to zombocom`), + ), + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/v2/some/fake-image/manifests/"+digest.String()), + ghttp.RespondWith(http.StatusOK, manifest), + ), + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/v2/some/fake-image/blobs/"+configDigest.String()), + ghttp.RespondWith(http.StatusOK, config), + ), + ) + + req.Source = resource.Source{ + Repository: registry.Addr() + "/some/fake-image", + } + + req.Version.Digest = digest.String() + }) + + AfterEach(func() { + registry.Close() + }) + + When("the certificate is provided in 'source'", func() { + BeforeEach(func() { + certPem := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: registry.HTTPTestServer.Certificate().Raw, + }) + Expect(certPem).ToNot(BeEmpty()) + + req.Source.DomainCerts = []string{string(certPem)} + }) + + It("pulls the image from the registry", func() { + Expect(actualErr).ToNot(HaveOccurred()) + + Expect(res.Version).To(Equal(req.Version)) + }) + }) + + When("the certificate is missing in 'source'", func() { + It("exits non-zero and returns an error", func() { + Expect(actualErr).To(HaveOccurred()) + }) + }) + }) + Describe("using a mirror", func() { var mirror *ghttp.Server @@ -545,6 +654,8 @@ var _ = Describe("In", func() { }) It("pulls the image from the registry declared in the repository and not from the mirror", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res.Version).To(Equal(req.Version)) Expect(mirror.ReceivedRequests()).To(BeEmpty()) @@ -566,6 +677,8 @@ var _ = Describe("In", func() { }) It("saves the rootfs and metadata", func() { + Expect(actualErr).ToNot(HaveOccurred()) + _, err := os.Stat(rootfsPath("Dockerfile")) Expect(err).ToNot(HaveOccurred()) @@ -623,6 +736,8 @@ var _ = Describe("In", func() { }) It("pulls the image from the library", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res.Version).To(Equal(req.Version)) }) @@ -632,6 +747,8 @@ var _ = Describe("In", func() { }) It("names the image with the original repository and tag, not the mirror", func() { + Expect(actualErr).ToNot(HaveOccurred()) + tag, err := name.NewTag("fake-image:latest") Expect(err).ToNot(HaveOccurred()) @@ -673,6 +790,8 @@ var _ = Describe("In", func() { }) It("saves the rootfs and metadata", func() { + Expect(actualErr).ToNot(HaveOccurred()) + _, err := os.Stat(rootfsPath("Dockerfile")) Expect(err).ToNot(HaveOccurred()) @@ -708,6 +827,8 @@ var _ = Describe("In", func() { }) It("pulls the image from the library", func() { + Expect(actualErr).ToNot(HaveOccurred()) + Expect(res.Version).To(Equal(req.Version)) }) }) diff --git a/out_test.go b/out_test.go index 7f97c3b..ad44a0a 100644 --- a/out_test.go +++ b/out_test.go @@ -3,6 +3,7 @@ package resource_test import ( "bytes" "encoding/json" + "encoding/pem" "fmt" "io" "io/ioutil" @@ -366,6 +367,99 @@ var _ = Describe("Out", func() { Expect(actualErr).ToNot(HaveOccurred()) }) }) + + Context("using a registry with self-signed certificate", func() { + var registry *ghttp.Server + var randomImage v1.Image + + BeforeEach(func() { + registry = ghttp.NewTLSServer() + + req.Source = resource.Source{ + Repository: registry.Addr() + "/fake-image", + Tag: "some-tag", + } + + tag, err := name.NewTag(req.Source.Name()) + Expect(err).ToNot(HaveOccurred()) + + randomImage, err = random.Image(1024, 1) + Expect(err).ToNot(HaveOccurred()) + + err = tarball.WriteToFile(filepath.Join(srcDir, "image.tar"), tag, randomImage) + Expect(err).ToNot(HaveOccurred()) + + req.Params.Image = "image.tar" + + layers, err := randomImage.Layers() + Expect(err).ToNot(HaveOccurred()) + + configDigest, err := randomImage.ConfigName() + Expect(err).ToNot(HaveOccurred()) + + registry.RouteToHandler("GET", "/v2/", func(w http.ResponseWriter, r *http.Request) { + ghttp.RespondWith(http.StatusOK, "welcome to zombocom")(w, r) + }) + + registry.RouteToHandler("HEAD", "/v2/fake-image/blobs/"+configDigest.String(), func(w http.ResponseWriter, r *http.Request) { + ghttp.RespondWith(http.StatusOK, "blob totally exists")(w, r) + }) + + registry.RouteToHandler("POST", "/v2/fake-image/blobs/uploads/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Location", "/upload/some-blob") + w.WriteHeader(http.StatusAccepted) + }) + + registry.RouteToHandler("PATCH", "/upload/some-blob", func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Location", "/commit/some-blob") + w.WriteHeader(http.StatusAccepted) + }) + + registry.RouteToHandler("PUT", "/commit/some-blob", func(w http.ResponseWriter, r *http.Request) { + ghttp.RespondWith(http.StatusCreated, "upload complete")(w, r) + + }) + + for _, l := range layers { + layerDigest, err := l.Digest() + Expect(err).ToNot(HaveOccurred()) + + registry.RouteToHandler("HEAD", "/v2/fake-image/blobs/"+layerDigest.String(), func(w http.ResponseWriter, r *http.Request) { + ghttp.RespondWith(http.StatusNotFound, "needs upload")(w, r) + }) + } + + registry.RouteToHandler("PUT", "/v2/fake-image/manifests/some-tag", func(w http.ResponseWriter, r *http.Request) { + ghttp.RespondWith(http.StatusOK, "manifest updated")(w, r) + }) + }) + + AfterEach(func() { + registry.Close() + }) + + When("the certificate is provided in 'source'", func() { + BeforeEach(func() { + certPem := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: registry.HTTPTestServer.Certificate().Raw, + }) + Expect(certPem).ToNot(BeEmpty()) + + req.Source.DomainCerts = []string{string(certPem)} + }) + + It("should not error", func() { + Expect(actualErr).ToNot(HaveOccurred()) + }) + }) + + When("the certificate is missing in 'source'", func() { + It("exits non-zero and returns an error", func() { + Expect(actualErr).To(HaveOccurred()) + }) + }) + }) }) func parallelTag(tag string) string {