Skip to content

Commit

Permalink
Improve create-stack build time by using OCI layout
Browse files Browse the repository at this point in the history
The ihop.Client.Build method will now export the image from the Docker
daemon as soon as the build completes, writing it to disk in OCI layout
format. From this point on, the create-stack codebase will interact
exclusively with the OCI layout image format. The advantage here is that
we won't be shuttling an image back and forth between the Docker daemon
and program memory or disk for every operation we need to perform.
Instead, we can just manipulate the image on the local filesystem
directly using the GGCR layout package.
  • Loading branch information
Ryan Moran authored and sophiewigmore committed Dec 12, 2022
1 parent 83cee2a commit 125cc77
Show file tree
Hide file tree
Showing 22 changed files with 280 additions and 321 deletions.
20 changes: 7 additions & 13 deletions commands/create_stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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)
Expand All @@ -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
}
1 change: 0 additions & 1 deletion integration/create_stack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
))
})
})
Expand Down
6 changes: 3 additions & 3 deletions internal/fakes/downloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

type Downloader struct {
DropCall struct {
sync.Mutex
mutex sync.Mutex
CallCount int
Receives struct {
Root string
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions internal/fakes/executable.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

type Executable struct {
ExecuteCall struct {
sync.Mutex
mutex sync.Mutex
CallCount int
Receives struct {
Execution pexec.Execution
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions internal/ihop/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
6 changes: 3 additions & 3 deletions internal/ihop/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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{
Expand All @@ -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() {
Expand Down
4 changes: 2 additions & 2 deletions internal/ihop/cataloger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
31 changes: 28 additions & 3 deletions internal/ihop/cataloger_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package ihop_test

import (
"os"
"path/filepath"
"testing"

"github.com/paketo-buildpacks/jam/internal/ihop"
Expand All @@ -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",
Expand All @@ -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")))
})
})
Expand Down
Loading

0 comments on commit 125cc77

Please sign in to comment.