diff --git a/commands/create_stack.go b/commands/create_stack.go index ecf0ad9..16f1db8 100644 --- a/commands/create_stack.go +++ b/commands/create_stack.go @@ -57,7 +57,13 @@ func createStack() *cobra.Command { } func createStackRun(flags createStackFlags) error { - definition, err := ihop.NewDefinitionFromFile(flags.config, flags.unbuffered, flags.secrets...) + logger := scribe.NewLogger(os.Stdout) + + if flags.unbuffered { + logger.Process("WARNING: The --unbuffered flag is deprecated. You can safely remove it.") + } + + definition, err := ihop.NewDefinitionFromFile(flags.config, flags.secrets...) if err != nil { return err } @@ -76,7 +82,6 @@ func createStackRun(flags createStackFlags) error { } builder := ihop.NewBuilder(client, ihop.Cataloger{}, runtime.NumCPU()) - logger := scribe.NewLogger(os.Stdout) creator := ihop.NewCreator(client, builder, ihop.UserLayerCreator{}, ihop.SBOMLayerCreator{}, ihop.OsReleaseLayerCreator{Def: definition}, time.Now, logger) stack, err := creator.Execute(definition) @@ -96,16 +101,5 @@ func createStackRun(flags createStackFlags) error { return err } - logger.Process("Cleaning up intermediate image artifacts") - err = client.Cleanup(stack.Build...) - if err != nil { - return err - } - - err = client.Cleanup(stack.Run...) - if err != nil { - return err - } - return nil } diff --git a/integration/create_stack_test.go b/integration/create_stack_test.go index a9a7177..cd194a3 100644 --- a/integration/create_stack_test.go +++ b/integration/create_stack_test.go @@ -261,7 +261,6 @@ func testCreateStack(t *testing.T, _ spec.G, it spec.S) { "", fmt.Sprintf(" Exporting build image to %s", filepath.Join(tmpDir, "build.oci")), fmt.Sprintf(" Exporting run image to %s", filepath.Join(tmpDir, "run.oci")), - " Cleaning up intermediate image artifacts", )) }) }) diff --git a/internal/fakes/downloader.go b/internal/fakes/downloader.go index 893ef2a..29d5786 100644 --- a/internal/fakes/downloader.go +++ b/internal/fakes/downloader.go @@ -7,7 +7,7 @@ import ( type Downloader struct { DropCall struct { - sync.Mutex + mutex sync.Mutex CallCount int Receives struct { Root string @@ -22,8 +22,8 @@ type Downloader struct { } func (f *Downloader) Drop(param1 string, param2 string) (io.ReadCloser, error) { - f.DropCall.Lock() - defer f.DropCall.Unlock() + f.DropCall.mutex.Lock() + defer f.DropCall.mutex.Unlock() f.DropCall.CallCount++ f.DropCall.Receives.Root = param1 f.DropCall.Receives.Uri = param2 diff --git a/internal/fakes/executable.go b/internal/fakes/executable.go index bc53188..5603000 100644 --- a/internal/fakes/executable.go +++ b/internal/fakes/executable.go @@ -8,7 +8,7 @@ import ( type Executable struct { ExecuteCall struct { - sync.Mutex + mutex sync.Mutex CallCount int Receives struct { Execution pexec.Execution @@ -21,8 +21,8 @@ type Executable struct { } func (f *Executable) Execute(param1 pexec.Execution) error { - f.ExecuteCall.Lock() - defer f.ExecuteCall.Unlock() + f.ExecuteCall.mutex.Lock() + defer f.ExecuteCall.mutex.Unlock() f.ExecuteCall.CallCount++ f.ExecuteCall.Receives.Execution = param1 if f.ExecuteCall.Stub != nil { diff --git a/internal/ihop/builder.go b/internal/ihop/builder.go index e1bdf50..88527cd 100644 --- a/internal/ihop/builder.go +++ b/internal/ihop/builder.go @@ -2,7 +2,7 @@ package ihop //go:generate faux --interface ImageScanner --output fakes/image_scanner.go type ImageScanner interface { - Scan(tag string) (SBOM, error) + Scan(path string) (SBOM, error) } //go:generate faux --interface ImageBuildPromise --output fakes/image_build_promise.go @@ -63,7 +63,7 @@ func (b Builder) build(def DefinitionImage, platform string) (Image, SBOM, error return Image{}, SBOM{}, err } - sbom, err := b.scanner.Scan(image.Tag) + sbom, err := b.scanner.Scan(image.Path) if err != nil { return Image{}, SBOM{}, err } diff --git a/internal/ihop/builder_test.go b/internal/ihop/builder_test.go index 6b38f3a..675962b 100644 --- a/internal/ihop/builder_test.go +++ b/internal/ihop/builder_test.go @@ -24,7 +24,7 @@ func testBuilder(t *testing.T, context spec.G, it spec.S) { it.Before(func() { client = &fakes.ImageClient{} - client.BuildCall.Returns.Image = ihop.Image{Tag: "some-image-tag"} + client.BuildCall.Returns.Image = ihop.Image{Path: "some-image-path"} scanner = &fakes.ImageScanner{} scanner.ScanCall.Returns.SBOM = ihop.NewSBOM(sbom.SBOM{ @@ -44,7 +44,7 @@ func testBuilder(t *testing.T, context spec.G, it spec.S) { image, bom, err := promise.Resolve() Expect(err).NotTo(HaveOccurred()) Expect(image).To(Equal(ihop.Image{ - Tag: "some-image-tag", + Path: "some-image-path", })) Expect(bom).To(Equal(ihop.NewSBOM(sbom.SBOM{ Artifacts: sbom.Artifacts{ @@ -59,7 +59,7 @@ func testBuilder(t *testing.T, context spec.G, it spec.S) { Expect(client.BuildCall.Receives.Platform).To(Equal("some-platform")) Expect(scanner.ScanCall.CallCount).To(Equal(1)) - Expect(scanner.ScanCall.Receives.Tag).To(Equal("some-image-tag")) + Expect(scanner.ScanCall.Receives.Path).To(Equal("some-image-path")) }) context("failure cases", func() { diff --git a/internal/ihop/cataloger.go b/internal/ihop/cataloger.go index c3f919d..4fb7564 100644 --- a/internal/ihop/cataloger.go +++ b/internal/ihop/cataloger.go @@ -14,8 +14,8 @@ import ( type Cataloger struct{} // Scan generates an SBOM for an image tagged in the Docker daemon. -func (c Cataloger) Scan(tag string) (SBOM, error) { - input, err := source.ParseInput(fmt.Sprintf("docker:%s", tag), "", false) +func (c Cataloger) Scan(path string) (SBOM, error) { + input, err := source.ParseInput(fmt.Sprintf("oci-dir:%s", path), "", false) if err != nil { return SBOM{}, err } diff --git a/internal/ihop/cataloger_test.go b/internal/ihop/cataloger_test.go index 23f13a5..f6e94a0 100644 --- a/internal/ihop/cataloger_test.go +++ b/internal/ihop/cataloger_test.go @@ -1,6 +1,8 @@ package ihop_test import ( + "os" + "path/filepath" "testing" "github.com/paketo-buildpacks/jam/internal/ihop" @@ -14,11 +16,34 @@ func testCataloger(t *testing.T, context spec.G, it spec.S) { Expect = NewWithT(t).Expect cataloger ihop.Cataloger + client ihop.Client + dir string ) context("Scan", func() { + it.Before(func() { + var err error + dir, err = os.MkdirTemp("", "") + Expect(err).NotTo(HaveOccurred()) + + client, err = ihop.NewClient(dir) + Expect(err).NotTo(HaveOccurred()) + }) + + it.After(func() { + Expect(os.RemoveAll(dir)).To(Succeed()) + }) + it("returns a bill of materials for an image", func() { - bom, err := cataloger.Scan("ubuntu:jammy") + err := os.WriteFile(filepath.Join(dir, "Dockerfile"), []byte("FROM ubuntu:jammy\nUSER some-user:some-group"), 0600) + Expect(err).NotTo(HaveOccurred()) + + image, err := client.Build(ihop.DefinitionImage{ + Dockerfile: filepath.Join(dir, "Dockerfile"), + }, "linux/amd64") + Expect(err).NotTo(HaveOccurred()) + + bom, err := cataloger.Scan(image.Path) Expect(err).NotTo(HaveOccurred()) Expect(bom.Packages()).To(ContainElements( "apt", @@ -27,9 +52,9 @@ func testCataloger(t *testing.T, context spec.G, it spec.S) { }) context("failure cases", func() { - context("when the tag cannot be parsed", func() { + context("when the oci layout cannot be scanned", func() { it("returns an error", func() { - _, err := cataloger.Scan("not a valid tag") + _, err := cataloger.Scan("not a valid path") Expect(err).To(MatchError(ContainSubstring("could not fetch image"))) }) }) diff --git a/internal/ihop/client.go b/internal/ihop/client.go index 72315b9..aed8d13 100644 --- a/internal/ihop/client.go +++ b/internal/ihop/client.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "io/fs" + "log" "math/big" "net" "os" @@ -41,7 +42,6 @@ import ( // An Image is a representation of a container image that can be built, // updated, or exported to an OCI-archive format. type Image struct { - Tag string Digest string OS string Architecture string @@ -52,27 +52,55 @@ type Image struct { Layers []Layer - unbuffered bool + Actual v1.Image + Path string } -// ToDaemonImage returns the GGCR v1.Image associated with this Image. -func (i Image) ToDaemonImage() (v1.Image, error) { - ref, err := name.ParseReference(i.Tag) +func FromImage(path string, image v1.Image) (Image, error) { + file, err := image.ConfigFile() if err != nil { - return nil, err + return Image{}, err } - option := daemon.WithBufferedOpener() - if i.unbuffered { - option = daemon.WithUnbufferedOpener() + labels := file.Config.Labels + if labels == nil { + labels = make(map[string]string) } - image, err := daemon.Image(ref, option) + ls, err := image.Layers() if err != nil { - return nil, err + return Image{}, err } - return image, nil + var layers []Layer + for _, layer := range ls { + diffID, err := layer.DiffID() + if err != nil { + return Image{}, err + } + + layers = append(layers, Layer{ + DiffID: diffID.String(), + Layer: layer, + }) + } + + digest, err := image.Digest() + if err != nil { + return Image{}, err + } + + return Image{ + Digest: digest.String(), + Env: file.Config.Env, + Labels: labels, + Layers: layers, + User: file.Config.User, + OS: file.OS, + Architecture: file.Architecture, + Actual: image, + Path: path, + }, nil } // A Layer is a representation of a container image layer. @@ -287,30 +315,66 @@ func (c Client) Build(def DefinitionImage, platform string) (Image, error) { } } - // fetch and return a reference to the built image - return c.get(Image{Tag: tag, unbuffered: def.unbuffered}) -} + defer func() { + _, err := c.docker.ImageRemove(context.Background(), tag, types.ImageRemoveOptions{}) + if err != nil { + log.Fatalln(err) + } + }() -// Update will apply any modifications made to the Image reference onto the -// actual container image in the Docker daemon. -func (c Client) Update(image Image) (Image, error) { - // Add a random tag to the original image to distinguish it from other - // identical images - random, err := randomName() + ref, err := name.ParseReference(tag) + if err != nil { + return Image{}, err + } + + image, err := daemon.Image(ref) + if err != nil { + return Image{}, err + } + + name, err := randomName() + if err != nil { + return Image{}, err + } + + path := filepath.Join(c.dir, name) + index, err := layout.Write(path, empty.Index) + if err != nil { + return Image{}, fmt.Errorf("failed to write image layout: %w", err) + } + + file, err := image.ConfigFile() if err != nil { return Image{}, err } - originalImage := fmt.Sprintf("%s:%s", image.Tag, random) - err = c.docker.ImageTag(context.Background(), image.Tag, originalImage) + + err = index.AppendImage(image, layout.WithPlatform(v1.Platform{ + OS: file.OS, + Architecture: file.Architecture, + })) if err != nil { return Image{}, err } - img, err := image.ToDaemonImage() + digest, err := image.Digest() + if err != nil { + return Image{}, err + } + + image, err = index.Image(digest) if err != nil { return Image{}, err } + // fetch and return a reference to the built image + return FromImage(string(path), image) +} + +// Update will apply any modifications made to the Image reference onto the +// actual container image in the Docker daemon. +func (c Client) Update(image Image) (Image, error) { + img := image.Actual + configFile, err := img.ConfigFile() if err != nil { return Image{}, err @@ -331,7 +395,7 @@ func (c Client) Update(image Image) (Image, error) { _, err = img.LayerByDiffID(hash) if err != nil { - if strings.Contains(err.Error(), "not found") { + if strings.Contains(err.Error(), "unknown diffID") { layers = append(layers, layer.Layer) continue } @@ -350,22 +414,30 @@ func (c Client) Update(image Image) (Image, error) { return Image{}, err } - tag, err := name.NewTag(image.Tag) + path, err := layout.FromPath(image.Path) if err != nil { - return Image{}, err + return Image{}, fmt.Errorf("could not load layout from path %q: %w", image.Path, err) } - _, err = daemon.Write(tag, updatedImage) + err = path.AppendImage(updatedImage, layout.WithPlatform(v1.Platform{ + OS: image.OS, + Architecture: image.Architecture, + })) + if err != nil { + return Image{}, fmt.Errorf("could not append image to layout: %w", err) + } + + digest, err := updatedImage.Digest() if err != nil { return Image{}, err } - err = c.Cleanup(Image{Tag: originalImage}) + updatedImage, err = path.Image(digest) if err != nil { return Image{}, err } - return c.get(image) + return FromImage(image.Path, updatedImage) } // Export creates an OCI-archive tarball at the path location that includes the @@ -388,12 +460,7 @@ func (c Client) Export(path string, images ...Image) error { } for _, image := range images { - img, err := image.ToDaemonImage() - if err != nil { - return err - } - - err = index.AppendImage(img, layout.WithPlatform(v1.Platform{ + err = index.AppendImage(image.Actual, layout.WithPlatform(v1.Platform{ OS: image.OS, Architecture: image.Architecture, })) @@ -457,71 +524,6 @@ func (c Client) Export(path string, images ...Image) error { return nil } -// Cleanup deletes the container images from the Docker daemon that are -// referenced by the given Images. -func (c Client) Cleanup(images ...Image) error { - for _, image := range images { - _, err := c.docker.ImageRemove(context.Background(), image.Tag, types.ImageRemoveOptions{}) - if err != nil && !docker.IsErrNotFound(err) { - return err - } - } - - return nil -} - -func (c Client) get(img Image) (Image, error) { - image, err := img.ToDaemonImage() - if err != nil { - return Image{}, err - } - - file, err := image.ConfigFile() - if err != nil { - return Image{}, err - } - - labels := file.Config.Labels - if labels == nil { - labels = make(map[string]string) - } - - ls, err := image.Layers() - if err != nil { - return Image{}, err - } - - var layers []Layer - for _, layer := range ls { - diffID, err := layer.DiffID() - if err != nil { - return Image{}, err - } - - layers = append(layers, Layer{ - DiffID: diffID.String(), - Layer: layer, - }) - } - - digest, err := image.Digest() - if err != nil { - return Image{}, err - } - - return Image{ - Digest: digest.String(), - Env: file.Config.Env, - Labels: labels, - Layers: layers, - Tag: img.Tag, - User: file.Config.User, - OS: file.OS, - Architecture: file.Architecture, - unbuffered: img.unbuffered, - }, nil -} - const letterBytes = "abcdefghijklmnopqrstuvwxyz0123456789" func randomName() (string, error) { diff --git a/internal/ihop/client_test.go b/internal/ihop/client_test.go index 5118c06..1a3a7fe 100644 --- a/internal/ihop/client_test.go +++ b/internal/ihop/client_test.go @@ -2,18 +2,11 @@ package ihop_test import ( "archive/tar" - ctx "context" - "fmt" "os" - "os/exec" "path/filepath" "testing" - "github.com/docker/docker/api/types" - docker "github.com/docker/docker/client" - "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/daemon" "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/paketo-buildpacks/jam/internal/ihop" @@ -43,16 +36,6 @@ func testClient(t *testing.T, context spec.G, it spec.S) { }) it.After(func() { - cli, err := docker.NewClientWithOpts(docker.FromEnv, docker.WithAPIVersionNegotiation()) - Expect(err).NotTo(HaveOccurred()) - - for _, image := range images { - _, err = cli.ImageRemove(ctx.Background(), image.Tag, types.ImageRemoveOptions{}) - if !docker.IsErrNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - } - } - Expect(os.RemoveAll(dir)).To(Succeed()) }) @@ -110,7 +93,6 @@ LABEL testing.build.arg.slice.key=$test_build_slice_arg`), 0600) images = append(images, image) - Expect(image.Tag).To(MatchRegexp(`^paketo\.io/stack/[a-z0-9]{10}$`)) Expect(image.Labels).To(HaveKeyWithValue("testing.key", "some-value")) Expect(image.Labels).To(HaveKeyWithValue("testing.build.arg.key", "1")) Expect(image.Labels).To(HaveKeyWithValue("testing.build.arg.slice.key", "1 2")) @@ -118,13 +100,7 @@ LABEL testing.build.arg.slice.key=$test_build_slice_arg`), 0600) Expect(image.OS).To(Equal("linux")) Expect(image.Architecture).To(Equal("arm64")) - ref, err := name.ParseReference(image.Tag) - Expect(err).NotTo(HaveOccurred()) - - img, err := daemon.Image(ref) - Expect(err).NotTo(HaveOccurred()) - - digest, err := img.Digest() + digest, err := image.Actual.Digest() Expect(err).NotTo(HaveOccurred()) Expect(image.Digest).To(Equal(digest.String())) @@ -147,10 +123,7 @@ RUN --mount=type=secret,id=test-secret,dst=/temp cat /temp > /secret`), 0600) images = append(images, image) - contents, err := exec.Command("docker", "run", "--rm", image.Tag, "cat", "/secret").CombinedOutput() - Expect(err).NotTo(HaveOccurred(), string(contents)) - - Expect(string(contents)).To(Equal("some-secret")) + Expect(image.Actual).To(HaveFileWithContent("/secret", ContainSubstring("some-secret"))) }) }) @@ -196,6 +169,33 @@ RUN --mount=type=secret,id=test-secret,dst=/temp cat /temp > /secret`), 0600) Expect(err).To(MatchError(ContainSubstring("executor failed running"))) }) }) + + context("when the layout cannot be written", func() { + var tmp string + + it.Before(func() { + var err error + tmp, err = os.MkdirTemp("", "") + Expect(err).NotTo(HaveOccurred()) + + Expect(os.Chmod(tmp, 0000)).To(Succeed()) + + client, err = ihop.NewClient(tmp) + Expect(err).NotTo(HaveOccurred()) + }) + + it.After(func() { + Expect(os.RemoveAll(tmp)).To(Succeed()) + }) + + it("returns an error", func() { + _, err := client.Build(ihop.DefinitionImage{ + Dockerfile: filepath.Join(dir, "Dockerfile"), + }, "linux/amd64") + Expect(err).To(MatchError(ContainSubstring("failed to write image layout"))) + Expect(err).To(MatchError(ContainSubstring("permission denied"))) + }) + }) }) }) @@ -306,13 +306,7 @@ RUN --mount=type=secret,id=test-secret,dst=/temp cat /temp > /secret`), 0600) images = append(images, image) - ref, err := name.ParseReference(image.Tag) - Expect(err).NotTo(HaveOccurred()) - - img, err := daemon.Image(ref) - Expect(err).NotTo(HaveOccurred()) - - Expect(img).To(HaveFileWithContent("/some/file", ContainSubstring("some-layer-content"))) + Expect(image.Actual).To(HaveFileWithContent("/some/file", ContainSubstring("some-layer-content"))) }) }) @@ -320,11 +314,7 @@ RUN --mount=type=secret,id=test-secret,dst=/temp cat /temp > /secret`), 0600) var image2 ihop.Image it.Before(func() { - contents, err := exec.Command("docker", "tag", fmt.Sprintf("%s:latest", image.Tag), "image2:latest").CombinedOutput() - Expect(err).NotTo(HaveOccurred(), string(contents)) - image2 = image - image2.Tag = "image2" images = append(images, image2) }) @@ -338,33 +328,31 @@ RUN --mount=type=secret,id=test-secret,dst=/temp cat /temp > /secret`), 0600) }) context("failure cases", func() { - - context("when the image tag cannot be parsed", func() { - it("returns an error", func() { - _, err := client.Update(ihop.Image{Tag: "not a valid tag"}) - Expect(err).To(MatchError(ContainSubstring("invalid reference format"))) - }) - }) - context("when the image layer diff ID is not valid", func() { var img ihop.Image + it.Before(func() { img = image img.Layers[0].DiffID = "this is not a diff id" }) - it.After(func() { - daemonImage, err := img.ToDaemonImage() - Expect(err).NotTo(HaveOccurred()) - - configName, _ := daemonImage.ConfigName() - images = append(images, ihop.Image{Tag: configName.String()}) - }) it("returns an error", func() { _, err := client.Update(img) Expect(err).To(MatchError(ContainSubstring("cannot parse hash"))) }) }) + + context("when the image cannot be found on its path", func() { + it.Before(func() { + image.Path = "/this/is/a/made/up/path" + }) + + it("returns an error", func() { + _, err := client.Update(image) + Expect(err).To(MatchError(ContainSubstring(`could not load layout from path "/this/is/a/made/up/path"`))) + Expect(err).To(MatchError(ContainSubstring("no such file or directory"))) + }) + }) }) }) @@ -441,81 +429,22 @@ RUN --mount=type=secret,id=test-secret,dst=/temp cat /temp > /secret`), 0600) }) }) - context("Cleanup", func() { - it.Before(func() { - err := os.WriteFile(filepath.Join(dir, "Dockerfile"), []byte("FROM scratch\nCOPY Dockerfile .\nUSER some-user:some-group"), 0600) - Expect(err).NotTo(HaveOccurred()) - - for _, platform := range []string{"linux/amd64", "linux/arm64"} { - image, err := client.Build(ihop.DefinitionImage{Dockerfile: filepath.Join(dir, "Dockerfile")}, platform) - Expect(err).NotTo(HaveOccurred()) - - images = append(images, image) - } - }) - - it.After(func() { - images = nil - }) - - it("removes the given images", func() { - Expect(client.Cleanup(images...)).To(Succeed()) - - cli, err := docker.NewClientWithOpts(docker.FromEnv, docker.WithAPIVersionNegotiation()) - Expect(err).NotTo(HaveOccurred()) - - for _, image := range images { - _, _, err := cli.ImageInspectWithRaw(ctx.Background(), image.Tag) - Expect(docker.IsErrNotFound(err)).To(BeTrue()) - } - }) - }) - context("Image", func() { - context("ToDaemonImage", func() { - var image ihop.Image + context("FromImage", func() { + var partial ihop.Image it.Before(func() { err := os.WriteFile(filepath.Join(dir, "Dockerfile"), []byte("FROM scratch\nCOPY Dockerfile .\nUSER some-user:some-group"), 0600) Expect(err).NotTo(HaveOccurred()) - image, err = client.Build(ihop.DefinitionImage{Dockerfile: filepath.Join(dir, "Dockerfile")}, "linux/amd64") + partial, err = client.Build(ihop.DefinitionImage{Dockerfile: filepath.Join(dir, "Dockerfile")}, "linux/amd64") Expect(err).NotTo(HaveOccurred()) - - images = append(images, image) }) - it("returns a v1.Image from the docker daemon", func() { - img, err := image.ToDaemonImage() - Expect(err).NotTo(HaveOccurred()) - - digest, err := img.Digest() + it("hydrates an Image from a partially populated one", func() { + image, err := ihop.FromImage(partial.Path, partial.Actual) Expect(err).NotTo(HaveOccurred()) - Expect(digest.String()).To(Equal(image.Digest)) - }) - - context("failure cases", func() { - context("when the image tag cannot be parsed", func() { - it.Before(func() { - image.Tag = "not a valid tag" - }) - - it("returns an error", func() { - _, err := image.ToDaemonImage() - Expect(err).To(MatchError(ContainSubstring("could not parse reference"))) - }) - }) - - context("when the image does not exist in the daemon", func() { - it.Before(func() { - image.Tag = "no-such-image" - }) - - it("returns an error", func() { - _, err := image.ToDaemonImage() - Expect(err).To(MatchError(ContainSubstring("No such image"))) - }) - }) + Expect(image).To(Equal(partial)) }) }) }) diff --git a/internal/ihop/creator_test.go b/internal/ihop/creator_test.go index 3d3d2f9..78fa606 100644 --- a/internal/ihop/creator_test.go +++ b/internal/ihop/creator_test.go @@ -75,7 +75,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { imageUpdateInvocations = append(imageUpdateInvocations, imageUpdateInvocation{ Image: ihop.Image{ Digest: image.Digest, - Tag: image.Tag, Layers: image.Layers, User: image.User, Env: append([]string{}, image.Env...), @@ -161,7 +160,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { promise := &fakes.ImageBuildPromise{} promise.ResolveCall.Returns.Image = ihop.Image{ Digest: imageDigest(), - Tag: fmt.Sprintf("image-%d", imageBuilder.ExecuteCall.CallCount), Labels: map[string]string{}, } promise.ResolveCall.Returns.SBOM = sboms[imageBuilder.ExecuteCall.CallCount-1] @@ -179,7 +177,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { userLayerCreateInvocations = append(userLayerCreateInvocations, layerCreateInvocation{ Image: ihop.Image{ Digest: image.Digest, - Tag: image.Tag, Layers: image.Layers, User: image.User, Env: append([]string{}, image.Env...), @@ -209,7 +206,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { sbomLayerCreateInvocations = append(sbomLayerCreateInvocations, layerCreateInvocation{ Image: ihop.Image{ Digest: image.Digest, - Tag: image.Tag, Layers: image.Layers, User: image.User, Env: append([]string{}, image.Env...), @@ -271,7 +267,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { Expect(stack).To(Equal(ihop.Stack{ Build: []ihop.Image{ { - Tag: "image-1", Digest: "image-digest-3", User: "1234:2345", Env: []string{ @@ -299,7 +294,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { }, Run: []ihop.Image{ { - Tag: "image-2", Digest: "image-digest-4", User: "3456:4567", Labels: map[string]string{ @@ -352,7 +346,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { Expect(userLayerCreator.CreateCall.CallCount).To(Equal(2)) Expect(userLayerCreateInvocations[0].Image.Digest).To(Equal("image-digest-1")) - Expect(userLayerCreateInvocations[0].Image.Tag).To(Equal("image-1")) Expect(userLayerCreateInvocations[0].Def).To(Equal(ihop.DefinitionImage{ Description: "some-stack-build-description", Dockerfile: "test-base-build-dockerfile-path", @@ -365,7 +358,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { })) Expect(userLayerCreateInvocations[0].SBOM).To(Equal(buildSBOM)) Expect(userLayerCreateInvocations[1].Image.Digest).To(Equal("image-digest-2")) - Expect(userLayerCreateInvocations[1].Image.Tag).To(Equal("image-2")) Expect(userLayerCreateInvocations[1].Def).To(Equal(ihop.DefinitionImage{ Description: "some-stack-run-description", Dockerfile: "test-base-run-dockerfile-path", @@ -380,7 +372,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { Expect(imageClient.UpdateCall.CallCount).To(Equal(2)) Expect(imageUpdateInvocations[0].Image.Digest).To(Equal("image-digest-1")) - Expect(imageUpdateInvocations[0].Image.Tag).To(Equal("image-1")) Expect(imageUpdateInvocations[0].Image.Labels).To(SatisfyAll( HaveKeyWithValue("io.buildpacks.stack.id", "some-stack-id"), HaveKeyWithValue("io.buildpacks.stack.description", "some-stack-build-description"), @@ -405,7 +396,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { "CNB_STACK_ID=some-stack-id", )) Expect(imageUpdateInvocations[1].Image.Digest).To(Equal("image-digest-2")) - Expect(imageUpdateInvocations[1].Image.Tag).To(Equal("image-2")) Expect(imageUpdateInvocations[1].Image.Labels).To(SatisfyAll( HaveKeyWithValue("io.buildpacks.stack.id", "some-stack-id"), HaveKeyWithValue("io.buildpacks.stack.description", "some-stack-run-description"), @@ -449,7 +439,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { Expect(stack).To(Equal(ihop.Stack{ Build: []ihop.Image{ { - Tag: "image-1", Digest: "image-digest-3", User: "1234:2345", Env: []string{ @@ -475,7 +464,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { }, }, { - Tag: "image-3", Digest: "image-digest-7", User: "1234:2345", Env: []string{ @@ -503,7 +491,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { }, Run: []ihop.Image{ { - Tag: "image-2", Digest: "image-digest-4", User: "3456:4567", Labels: map[string]string{ @@ -528,7 +515,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { }, }, { - Tag: "image-4", Digest: "image-digest-8", User: "3456:4567", Labels: map[string]string{ @@ -606,7 +592,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { Expect(imageClient.UpdateCall.CallCount).To(Equal(2)) Expect(imageUpdateInvocations[0].Image.Digest).To(Equal("image-digest-1")) - Expect(imageUpdateInvocations[0].Image.Tag).To(Equal("image-1")) Expect(imageUpdateInvocations[0].Image.Labels).To(HaveKeyWithValue("io.paketo.stack.packages", MatchJSON(`[ { "name": "some-build-package", @@ -630,7 +615,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { } ]`))) Expect(imageUpdateInvocations[1].Image.Digest).To(Equal("image-digest-2")) - Expect(imageUpdateInvocations[1].Image.Tag).To(Equal("image-2")) Expect(imageUpdateInvocations[1].Image.Labels).To(HaveKeyWithValue("io.paketo.stack.packages", MatchJSON(`[ { "name": "some-common-package", @@ -688,11 +672,9 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { Expect(imageClient.UpdateCall.CallCount).To(Equal(2)) Expect(imageUpdateInvocations[0].Image.Digest).To(Equal("image-digest-1")) - Expect(imageUpdateInvocations[0].Image.Tag).To(Equal("image-1")) Expect(imageUpdateInvocations[0].Image.Labels).To(HaveKeyWithValue("io.buildpacks.stack.mixins", MatchJSON(`["some-common-package","build:some-build-package"]`))) Expect(imageUpdateInvocations[1].Image.Digest).To(Equal("image-digest-2")) - Expect(imageUpdateInvocations[1].Image.Tag).To(Equal("image-2")) Expect(imageUpdateInvocations[1].Image.Labels).To(HaveKeyWithValue("io.buildpacks.stack.mixins", MatchJSON(`["some-common-package","run:some-run-package"]`))) }) }) @@ -737,13 +719,11 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { Expect(imageClient.UpdateCall.CallCount).To(Equal(2)) Expect(imageUpdateInvocations[0].Image.Digest).To(Equal("image-digest-1")) - Expect(imageUpdateInvocations[0].Image.Tag).To(Equal("image-1")) Expect(imageUpdateInvocations[0].Image.Labels).NotTo(HaveKey("io.buildpacks.base.sbom")) Expect(imageUpdateInvocations[0].Image.Layers).To(Equal([]ihop.Layer{ {DiffID: "build-user-layer-id"}, })) Expect(imageUpdateInvocations[1].Image.Digest).To(Equal("image-digest-2")) - Expect(imageUpdateInvocations[1].Image.Tag).To(Equal("image-2")) Expect(imageUpdateInvocations[1].Image.Labels).To(HaveKeyWithValue("io.buildpacks.base.sbom", "sbom-layer-id")) Expect(imageUpdateInvocations[1].Image.Layers).To(Equal([]ihop.Layer{ {DiffID: "run-user-layer-id"}, @@ -753,7 +733,6 @@ func testCreator(t *testing.T, context spec.G, it spec.S) { Expect(sbomLayerCreator.CreateCall.CallCount).To(Equal(1)) Expect(sbomLayerCreateInvocations[0].Image.Digest).To(Equal("image-digest-2")) - Expect(sbomLayerCreateInvocations[0].Image.Tag).To(Equal("image-2")) Expect(sbomLayerCreateInvocations[0].Image.Labels).NotTo(HaveKey("io.buildpacks.base.sbom")) Expect(sbomLayerCreateInvocations[0].Image.Layers).To(Equal([]ihop.Layer{ {DiffID: "run-user-layer-id"}, diff --git a/internal/ihop/definition.go b/internal/ihop/definition.go index ed971ee..bbe2d62 100644 --- a/internal/ihop/definition.go +++ b/internal/ihop/definition.go @@ -75,8 +75,6 @@ type DefinitionImage struct { // UID is the cnb user id to be specified in the image. UID int `toml:"uid"` - - unbuffered bool } // DefinitionDeprecated defines the deprecated features of the stack. @@ -128,7 +126,7 @@ func (i DefinitionImage) Arguments() ([]string, error) { } // NewDefinitionFromFile parses the stack descriptor from a file location. -func NewDefinitionFromFile(path string, unbuffered bool, secrets ...string) (Definition, error) { +func NewDefinitionFromFile(path string, secrets ...string) (Definition, error) { path, err := filepath.Abs(path) if err != nil { return Definition{}, err @@ -220,9 +218,6 @@ func NewDefinitionFromFile(path string, unbuffered bool, secrets ...string) (Def definition.Run.Secrets = definition.Build.Secrets } - definition.Build.unbuffered = unbuffered - definition.Run.unbuffered = unbuffered - return definition, nil } diff --git a/internal/ihop/definition_test.go b/internal/ihop/definition_test.go index 6659f38..b8496a3 100644 --- a/internal/ihop/definition_test.go +++ b/internal/ihop/definition_test.go @@ -67,7 +67,7 @@ platforms = ["some-stack-platform"] }) it("creates a definition from a config file", func() { - definition, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false) + definition, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml")) Expect(err).NotTo(HaveOccurred()) Expect(definition).To(Equal(ihop.Definition{ ID: "some-stack-id", @@ -126,7 +126,7 @@ homepage = "https://github.com/some-stack" }) it("sets the defaults", func() { - definition, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false) + definition, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml")) Expect(err).NotTo(HaveOccurred()) Expect(definition).To(Equal(ihop.Definition{ ID: "some-stack-id", @@ -173,7 +173,7 @@ homepage = "some-stack-homepage" }) it("returns an error", func() { - _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false) + _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml")) Expect(err).To(MatchError("failed to parse stack descriptor: 'id' is a required field")) }) }) @@ -198,7 +198,7 @@ homepage = "some-stack-homepage" }) it("returns an error", func() { - _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false) + _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml")) Expect(err).To(MatchError("failed to parse stack descriptor: 'build.dockerfile' is a required field")) }) }) @@ -223,7 +223,7 @@ homepage = "some-stack-homepage" }) it("returns an error", func() { - _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false) + _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml")) Expect(err).To(MatchError("failed to parse stack descriptor: 'build.uid' is a required field")) }) }) @@ -249,7 +249,7 @@ homepage = "some-stack-homepage" }) it("returns an error", func() { - _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false) + _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml")) Expect(err).To(MatchError("failed to parse stack descriptor: 'build.gid' is a required field")) }) }) @@ -274,7 +274,7 @@ homepage = "some-stack-homepage" }) it("returns an error", func() { - _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false) + _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml")) Expect(err).To(MatchError("failed to parse stack descriptor: 'run.dockerfile' is a required field")) }) }) @@ -299,7 +299,7 @@ homepage = "some-stack-homepage" }) it("returns an error", func() { - _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false) + _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml")) Expect(err).To(MatchError("failed to parse stack descriptor: 'run.uid' is a required field")) }) }) @@ -324,7 +324,7 @@ homepage = "some-stack-homepage" }) it("returns an error", func() { - _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false) + _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml")) Expect(err).To(MatchError("failed to parse stack descriptor: 'run.gid' is a required field")) }) }) @@ -332,7 +332,7 @@ homepage = "some-stack-homepage" context("when secrets are given", func() { it("includes them in the build/run image definitions", func() { - definition, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false, "first-secret=first-value", "second-secret=second-value") + definition, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), "first-secret=first-value", "second-secret=second-value") Expect(err).NotTo(HaveOccurred()) Expect(definition.Build.Secrets).To(Equal(map[string]string{ "first-secret": "first-value", @@ -375,7 +375,7 @@ homepage = "https://github.com/some-stack" }) it("transforms args to string", func() { - definition, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false) + definition, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml")) Expect(err).NotTo(HaveOccurred()) buildArgs, err := definition.Build.Arguments() Expect(err).NotTo(HaveOccurred()) @@ -409,7 +409,7 @@ homepage = "https://github.com/some-stack" }) it("returns an error", func() { - definition, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false) + definition, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml")) Expect(err).ToNot(HaveOccurred()) _, err = definition.Build.Arguments() @@ -446,7 +446,7 @@ homepage = "https://github.com/some-stack" }) it("returns an error", func() { - definition, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false) + definition, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml")) Expect(err).ToNot(HaveOccurred()) buildArgs, err := definition.Build.Arguments() @@ -462,7 +462,7 @@ homepage = "https://github.com/some-stack" context("failure cases", func() { context("when the file cannot be opened", func() { it("returns an error", func() { - _, err := ihop.NewDefinitionFromFile("this file does not exist", false) + _, err := ihop.NewDefinitionFromFile("this file does not exist") Expect(err).To(MatchError(ContainSubstring("no such file or directory"))) }) }) @@ -474,14 +474,14 @@ homepage = "https://github.com/some-stack" }) it("returns an error", func() { - _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false) + _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml")) Expect(err).To(MatchError(ContainSubstring("but got '%' instead"))) }) }) context("when a secret is malformed", func() { it("returns an error", func() { - _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), false, "this secret is malformed") + _, err := ihop.NewDefinitionFromFile(filepath.Join(dir, "stack.toml"), "this secret is malformed") Expect(err).To(MatchError("malformed secret: \"this secret is malformed\" must be in the form \"key=value\"")) }) }) diff --git a/internal/ihop/fakes/image_scanner.go b/internal/ihop/fakes/image_scanner.go index 0062abd..77ca35c 100644 --- a/internal/ihop/fakes/image_scanner.go +++ b/internal/ihop/fakes/image_scanner.go @@ -11,7 +11,7 @@ type ImageScanner struct { mutex sync.Mutex CallCount int Receives struct { - Tag string + Path string } Returns struct { SBOM ihop.SBOM @@ -25,7 +25,7 @@ func (f *ImageScanner) Scan(param1 string) (ihop.SBOM, error) { f.ScanCall.mutex.Lock() defer f.ScanCall.mutex.Unlock() f.ScanCall.CallCount++ - f.ScanCall.Receives.Tag = param1 + f.ScanCall.Receives.Path = param1 if f.ScanCall.Stub != nil { return f.ScanCall.Stub(param1) } diff --git a/internal/ihop/init_test.go b/internal/ihop/init_test.go index 92efc72..f45e85e 100644 --- a/internal/ihop/init_test.go +++ b/internal/ihop/init_test.go @@ -14,7 +14,7 @@ import ( . "github.com/onsi/gomega" ) -func TestIHOP(t *testing.T) { +func TestUnitIHOP(t *testing.T) { format.MaxLength = 0 var Expect = NewWithT(t).Expect diff --git a/internal/ihop/os_release_layer_creator.go b/internal/ihop/os_release_layer_creator.go index caa42d9..f4a8df0 100644 --- a/internal/ihop/os_release_layer_creator.go +++ b/internal/ihop/os_release_layer_creator.go @@ -16,10 +16,7 @@ type OsReleaseLayerCreator struct { } func (o OsReleaseLayerCreator) Create(image Image, _ DefinitionImage, _ SBOM) (Layer, error) { - img, err := image.ToDaemonImage() - if err != nil { - return Layer{}, err - } + img := image.Actual tarBuffer, err := os.CreateTemp("", "") if err != nil { diff --git a/internal/ihop/os_release_layer_creator_test.go b/internal/ihop/os_release_layer_creator_test.go index 7054144..b6ea370 100644 --- a/internal/ihop/os_release_layer_creator_test.go +++ b/internal/ihop/os_release_layer_creator_test.go @@ -3,6 +3,8 @@ package ihop_test import ( "archive/tar" "io" + "os" + "path/filepath" "testing" "github.com/paketo-buildpacks/jam/internal/ihop" @@ -16,8 +18,32 @@ func testOsReleaseLayerCreator(t *testing.T, context spec.G, it spec.S) { Expect = NewWithT(t).Expect creator ihop.OsReleaseLayerCreator + client ihop.Client + image ihop.Image + dir string ) + it.Before(func() { + var err error + dir, err = os.MkdirTemp("", "dockerfile-test") + Expect(err).NotTo(HaveOccurred()) + + client, err = ihop.NewClient(dir) + Expect(err).NotTo(HaveOccurred()) + + err = os.WriteFile(filepath.Join(dir, "Dockerfile"), []byte("FROM ubuntu:jammy\nUSER some-user:some-group"), 0600) + Expect(err).NotTo(HaveOccurred()) + + image, err = client.Build(ihop.DefinitionImage{ + Dockerfile: filepath.Join(dir, "Dockerfile"), + }, "linux/amd64") + Expect(err).NotTo(HaveOccurred()) + }) + + it.After(func() { + Expect(os.RemoveAll(dir)).To(Succeed()) + }) + it("creates a layer with adjusted /etc/os-release", func() { creator.Def = ihop.Definition{ ID: "some-stack-id", @@ -27,7 +53,7 @@ func testOsReleaseLayerCreator(t *testing.T, context spec.G, it spec.S) { BugReportURL: "some-stack-bug-report-url", } layer, err := creator.Create( - ihop.Image{Tag: "ubuntu:jammy"}, + image, ihop.DefinitionImage{}, ihop.SBOM{}, ) diff --git a/internal/ihop/sbom_test.go b/internal/ihop/sbom_test.go index 90f0b37..fd175ac 100644 --- a/internal/ihop/sbom_test.go +++ b/internal/ihop/sbom_test.go @@ -118,7 +118,7 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) { Expect(output).To(MatchJSON(`{ "artifacts": [ { - "id": "ad6a28f75514e659", + "id": "f0dc33bc5d7603be", "name": "a-package", "version": "", "type": "", @@ -147,7 +147,7 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) { } }, { - "id": "99337f6e42516f6f", + "id": "3967142b5e338b90", "name": "b-package", "version": "", "type": "", @@ -173,7 +173,7 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) { } }, { - "id": "e9f5fdd345ca49fd", + "id": "3ffc024cc467d6f2", "name": "c-package", "version": "", "type": "", diff --git a/internal/ihop/user_layer_creator.go b/internal/ihop/user_layer_creator.go index ef028f0..b45d457 100644 --- a/internal/ihop/user_layer_creator.go +++ b/internal/ihop/user_layer_creator.go @@ -16,10 +16,7 @@ type UserLayerCreator struct{} func (c UserLayerCreator) Create(image Image, def DefinitionImage, _ SBOM) (Layer, error) { files := make(map[*tar.Header]io.Reader) - img, err := image.ToDaemonImage() - if err != nil { - return Layer{}, err - } + img := image.Actual tarBuffer, err := os.CreateTemp("", "") if err != nil { diff --git a/internal/ihop/user_layer_creator_test.go b/internal/ihop/user_layer_creator_test.go index 6a7d3e9..38c05cb 100644 --- a/internal/ihop/user_layer_creator_test.go +++ b/internal/ihop/user_layer_creator_test.go @@ -3,6 +3,8 @@ package ihop_test import ( "archive/tar" "io" + "os" + "path/filepath" "testing" "github.com/paketo-buildpacks/jam/internal/ihop" @@ -16,11 +18,35 @@ func testUserLayerCreator(t *testing.T, context spec.G, it spec.S) { Expect = NewWithT(t).Expect creator ihop.UserLayerCreator + client ihop.Client + image ihop.Image + dir string ) + it.Before(func() { + var err error + dir, err = os.MkdirTemp("", "dockerfile-test") + Expect(err).NotTo(HaveOccurred()) + + client, err = ihop.NewClient(dir) + Expect(err).NotTo(HaveOccurred()) + + err = os.WriteFile(filepath.Join(dir, "Dockerfile"), []byte("FROM busybox\nUSER some-user:some-group"), 0600) + Expect(err).NotTo(HaveOccurred()) + + image, err = client.Build(ihop.DefinitionImage{ + Dockerfile: filepath.Join(dir, "Dockerfile"), + }, "linux/amd64") + Expect(err).NotTo(HaveOccurred()) + }) + + it.After(func() { + Expect(os.RemoveAll(dir)).To(Succeed()) + }) + it("creates a layer with user details", func() { layer, err := creator.Create( - ihop.Image{Tag: "busybox:latest"}, + image, ihop.DefinitionImage{ UID: 4567, GID: 1234, @@ -68,13 +94,4 @@ func testUserLayerCreator(t *testing.T, context spec.G, it spec.S) { Expect(headers["home/cnb"].Uid).To(Equal(4567)) Expect(headers["home/cnb"].Gid).To(Equal(1234)) }) - - context("failure cases", func() { - context("when the image does not exist on the daemon", func() { - it("returns an error", func() { - _, err := creator.Create(ihop.Image{Tag: "not an image"}, ihop.DefinitionImage{}, ihop.SBOM{}) - Expect(err).To(MatchError(ContainSubstring("could not parse reference"))) - }) - }) - }) } diff --git a/internal/image_test.go b/internal/image_test.go index 961d033..05df726 100644 --- a/internal/image_test.go +++ b/internal/image_test.go @@ -416,7 +416,6 @@ func testImage(t *testing.T, context spec.G, it spec.S) { context("image cannot be created from ref", func() { it("returns an error", func() { _, err := internal.GetBuildpackageID("index.docker.io/does-not-exist/go:0.5.0") - fmt.Println(err) Expect(err).To(MatchError(ContainSubstring("UNAUTHORIZED: authentication required"))) }) }) diff --git a/scripts/unit.sh b/scripts/unit.sh index 9d9bbba..e148fe3 100755 --- a/scripts/unit.sh +++ b/scripts/unit.sh @@ -50,7 +50,7 @@ function unit::run() { testout=$(mktemp) pushd "${BUILDPACKDIR}" > /dev/null - if go test ./... -v -run "Unit|Example" | tee "${testout}"; then + if go test ./... -v -count=1 -run "Unit|Example" | tee "${testout}"; then util::tools::tests::checkfocus "${testout}" util::print::success "** GO Test Succeeded **" else