Skip to content

Commit

Permalink
WIP - implementing the method to export the layout image to a tarball
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Bustamante <[email protected]>
  • Loading branch information
jjbustamante committed Jan 20, 2023
1 parent 4d50b9a commit f201427
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 37 deletions.
137 changes: 105 additions & 32 deletions layout/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"strings"
"time"

"github.com/google/go-containerregistry/pkg/name"

"github.com/google/go-containerregistry/pkg/v1/tarball"

v1 "github.com/google/go-containerregistry/pkg/v1"
Expand All @@ -23,17 +25,21 @@ var _ imgutil.Image = (*Image)(nil)

type Image struct {
v1.Image
createdAt time.Time
fileName string // use for exporting to tarball
path string
prevLayers []v1.Layer
createdAt time.Time
tag name.Reference // use for exporting to tarball
}

type imageOptions struct {
platform imgutil.Platform
baseImage v1.Image
baseImagePath string
prevImagePath string
createdAt time.Time
platform imgutil.Platform
prevImagePath string
tarFileName string
tarNameRef name.Reference
}

type ImageOption func(*imageOptions) error
Expand Down Expand Up @@ -76,6 +82,20 @@ func WithCreatedAt(createdAt time.Time) ImageOption {
}
}

// WithTarConfig lets a caller set file tarball file name and tag to be used when the image
// is saved with the method imgutil.SaveFile
// If fileName doesn't contain .tar extension it will be added
func WithTarConfig(fileName string, tag name.Reference) ImageOption {
return func(i *imageOptions) error {
if fileName != "" && filepath.Ext(fileName) != ".tar" {
fileName = fmt.Sprintf("%s.tar", fileName)
}
i.tarFileName = fileName
i.tarNameRef = tag
return nil
}
}

// FromBaseImagePath loads an existing image as the config and layers for the new underlyingImage.
// Ignored if underlyingImage is not found.
func FromBaseImagePath(path string) ImageOption {
Expand Down Expand Up @@ -104,8 +124,10 @@ func NewImage(path string, ops ...ImageOption) (*Image, error) {
}

ri := &Image{
Image: image,
path: path,
Image: image,
path: path,
fileName: imageOpts.tarFileName,
tag: imageOpts.tarNameRef,
}

if imageOpts.prevImagePath != "" {
Expand Down Expand Up @@ -523,33 +545,9 @@ func (i *Image) SaveAs(name string, additionalNames ...string) error {
log.Printf("multiple additional names %v are ignored when OCI layout is used", additionalNames)
}

err := i.mutateCreatedAt(i.Image, v1.Time{Time: i.createdAt})
if err != nil {
return errors.Wrap(err, "set creation time")
}

cfg, err := i.Image.ConfigFile()
if err != nil {
return errors.Wrap(err, "get image config")
}
cfg = cfg.DeepCopy()

layers, err := i.Image.Layers()
err := i.prepareImage()
if err != nil {
return errors.Wrap(err, "get image layers")
}
cfg.History = make([]v1.History, len(layers))
for j := range cfg.History {
cfg.History[j] = v1.History{
Created: v1.Time{Time: i.createdAt},
}
}

cfg.DockerVersion = ""
cfg.Container = ""
err = i.mutateConfigFile(i.Image, cfg)
if err != nil {
return errors.Wrap(err, "zeroing history")
return err
}

// initialize image path
Expand All @@ -572,7 +570,38 @@ func (i *Image) SaveAs(name string, additionalNames ...string) error {
}

func (i *Image) SaveFile() (string, error) {
return "", errors.New("not yet implemented")
err := i.validateTarInputs()
if err != nil {
return "", err
}

err = i.prepareImage()
if err != nil {
return "", err
}

fileName := filepath.Join(i.Name(), i.fileName)

err = os.MkdirAll(i.Name(), os.ModePerm)
if err != nil {
return "", errors.Wrapf(err, "creating destination folder %s", i.Name())
}

f, err := os.Create(fileName)
if err != nil {
return "", errors.Wrapf(err, "creating destination file %s", fileName)
}
defer f.Close()

var diagnostics []imgutil.SaveDiagnostic
if err := tarball.Write(i.tag, i.Image, f); err != nil {
diagnostics = append(diagnostics, imgutil.SaveDiagnostic{ImageName: fileName, Cause: err})
}
if len(diagnostics) > 0 {
return "", imgutil.SaveError{Errors: diagnostics}
}

return fileName, nil
}

func (i *Image) Delete() error {
Expand Down Expand Up @@ -739,3 +768,47 @@ func (i *Image) mutateImage(base v1.Image) {
Image: base,
}
}

// prepareImage prepare the internal images representation before saving
func (i *Image) prepareImage() error {
err := i.mutateCreatedAt(i.Image, v1.Time{Time: i.createdAt})
if err != nil {
return errors.Wrap(err, "set creation time")
}

cfg, err := i.Image.ConfigFile()
if err != nil {
return errors.Wrap(err, "get image config")
}
cfg = cfg.DeepCopy()

layers, err := i.Image.Layers()
if err != nil {
return errors.Wrap(err, "get image layers")
}
cfg.History = make([]v1.History, len(layers))
for j := range cfg.History {
cfg.History[j] = v1.History{
Created: v1.Time{Time: i.createdAt},
}
}

cfg.DockerVersion = ""
cfg.Container = ""
err = i.mutateConfigFile(i.Image, cfg)
if err != nil {
return errors.Wrap(err, "zeroing history")
}

return nil
}

func (i *Image) validateTarInputs() error {
if i.fileName == "" {
return errors.New("file name could not be empty when saving image as a tarball")
}
if i.tag == nil {
return errors.New("a tag must be provided when saving image as a tarball")
}
return nil
}
82 changes: 82 additions & 0 deletions layout/layout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"testing"
"time"

"github.com/google/go-containerregistry/pkg/name"

"github.com/google/go-containerregistry/pkg/v1/remote"

"github.com/buildpacks/imgutil"
Expand Down Expand Up @@ -969,4 +971,84 @@ func testImage(t *testing.T, when spec.G, it spec.S) {
})
})
})

when("#SaveFile", func() {
var tag name.Reference
var fileName string

it.After(func() {
os.RemoveAll(imagePath)
})

when("#FromBaseImage with full image", func() {
it.Before(func() {
imagePath = filepath.Join(tmpDir, "save-from-base-image")
tag, err = name.NewTag("my-app:latest")
fileName = "my-app"
h.AssertNil(t, err)
})

when("file name and tag are provided", func() {
when("file name do not have .tar extension", func() {
it.Before(func() {
fileName = "my-app"
})

it("saves the image in a tarball", func() {
image, err := layout.NewImage(imagePath, layout.FromBaseImage(testImage), layout.WithTarConfig(fileName, tag))
h.AssertNil(t, err)

// save tarball
output, err := image.SaveFile()
h.AssertNil(t, err)

h.AssertPathExists(t, output)
h.AssertEq(t, filepath.Base(output), "my-app.tar")
})
})

when("file name has .tar extension", func() {
it.Before(func() {
fileName = "my-app.tar"
})

it("saves the image in a tarball", func() {
image, err := layout.NewImage(imagePath, layout.FromBaseImage(testImage), layout.WithTarConfig(fileName, tag))
h.AssertNil(t, err)

// save tarball
output, err := image.SaveFile()
h.AssertNil(t, err)

h.AssertPathExists(t, output)
h.AssertEq(t, filepath.Base(output), "my-app.tar")
})
})
})

when("file name and tag are not provided", func() {
when("file name is not provided", func() {
it("error is thrown", func() {
image, err := layout.NewImage(imagePath, layout.FromBaseImage(testImage), layout.WithTarConfig("", tag))
h.AssertNil(t, err)

// save tarball
_, err = image.SaveFile()
h.AssertError(t, err, "file name could not be empty when saving image as a tarball")
})
})

when("tag is not provided", func() {
it("error is thrown", func() {
image, err := layout.NewImage(imagePath, layout.FromBaseImage(testImage), layout.WithTarConfig(fileName, nil))
h.AssertNil(t, err)

// save tarball
_, err = image.SaveFile()
h.AssertError(t, err, "a tag must be provided when saving image as a tarball")
})
})
})
})
})
}
3 changes: 2 additions & 1 deletion layout/util.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package layout

import (
"github.com/google/go-containerregistry/pkg/name"
"path/filepath"
"strings"

"github.com/google/go-containerregistry/pkg/name"
)

// ParseRefToPath parse the given image reference to local path directory following the rules:
Expand Down
10 changes: 6 additions & 4 deletions layout/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package layout_test

import (
"fmt"
"github.com/buildpacks/imgutil/layout"
h "github.com/buildpacks/imgutil/testhelpers"
"path/filepath"
"testing"

"github.com/google/go-containerregistry/pkg/name"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"path/filepath"
"testing"

"github.com/buildpacks/imgutil/layout"
h "github.com/buildpacks/imgutil/testhelpers"
)

const (
Expand Down

0 comments on commit f201427

Please sign in to comment.