Skip to content

Commit

Permalink
Merge pull request #238 from buildpacks/refactor/v1image
Browse files Browse the repository at this point in the history
Refactor: local images should be v1.Images
  • Loading branch information
natalieparellano authored Jan 16, 2024
2 parents b5b91d3 + e5101bc commit 6fa1289
Show file tree
Hide file tree
Showing 14 changed files with 3,970 additions and 2 deletions.
505 changes: 505 additions & 0 deletions cnb_image.go

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions fakes/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ func (i *Image) Identifier() (imgutil.Identifier, error) {
return i.identifier, nil
}

func (i *Image) Kind() string {
return ""
}

func (i *Image) UnderlyingImage() v1.Image {
return nil
}

func (i *Image) Rebase(baseTopLayer string, newBase imgutil.Image) error {
i.base = newBase.Name()
return nil
Expand Down
8 changes: 6 additions & 2 deletions image.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ type Image interface {
Env(key string) (string, error)
// Found tells whether the image exists in the repository by `Name()`.
Found() bool
// Valid returns true if the image is well-formed (e.g. all manifest layers exist on the registry).
Valid() bool
GetAnnotateRefName() (string, error)
// GetLayer retrieves layer by diff id. Returns a reader of the uncompressed contents of the layer.
GetLayer(diffID string) (io.ReadCloser, error)
History() ([]v1.History, error)
Identifier() (Identifier, error)
// Kind exposes the type of image that backs the imgutil.Image implementation.
// It could be `local`, `locallayout`, `remote`, or `layout`.
Kind() string
Label(string) (string, error)
Labels() (map[string]string, error)
// ManifestSize returns the size of the manifest. If a manifest doesn't exist, it returns 0.
Expand All @@ -37,6 +38,9 @@ type Image interface {
OSVersion() (string, error)
// TopLayer returns the diff id for the top layer
TopLayer() (string, error)
UnderlyingImage() v1.Image
// Valid returns true if the image is well-formed (e.g. all manifest layers exist on the registry).
Valid() bool
Variant() (string, error)
WorkingDir() (string, error)

Expand Down
8 changes: 8 additions & 0 deletions layout/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ func (i *Image) Identifier() (imgutil.Identifier, error) {
return newLayoutIdentifier(i.path, hash)
}

func (i *Image) Kind() string {
return `layout`
}

func (i *Image) Label(key string) (string, error) {
cfg, err := i.Image.ConfigFile()
if err != nil {
Expand Down Expand Up @@ -271,6 +275,10 @@ func (i *Image) TopLayer() (string, error) {
return hex.String(), nil
}

func (i *Image) UnderlyingImage() v1.Image {
return i.Image
}

func (i *Image) Variant() (string, error) {
cfg, err := i.Image.ConfigFile()
if err != nil {
Expand Down
8 changes: 8 additions & 0 deletions local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ func (i *Image) Identifier() (imgutil.Identifier, error) {
}, nil
}

func (i *Image) Kind() string {
return `local`
}

func (i *Image) Label(key string) (string, error) {
labels := i.inspect.Config.Labels
return labels[key], nil
Expand Down Expand Up @@ -155,6 +159,10 @@ func (i *Image) TopLayer() (string, error) {
return topLayer, nil
}

func (i *Image) UnderlyingImage() v1.Image {
return nil
}

func (i *Image) Variant() (string, error) {
return i.inspect.Variant, nil
}
Expand Down
112 changes: 112 additions & 0 deletions locallayout/image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package locallayout

import (
"errors"
"fmt"
"io"
"strings"
"sync"

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

"github.com/buildpacks/imgutil"
)

// Image wraps an imgutil.CNBImageCore and implements the methods needed to complete the imgutil.Image interface.
type Image struct {
*imgutil.CNBImageCore
lastIdentifier string
daemonOS string
downloadOnce *sync.Once
}

var _ imgutil.Image = &Image{}

func (i *Image) Found() bool {
return i.lastIdentifier != ""
}

func (i *Image) Identifier() (imgutil.Identifier, error) {
return idStringer{
id: strings.TrimPrefix(i.lastIdentifier, "sha256:"),
}, nil
}

type idStringer struct {
id string
}

func (i idStringer) String() string {
return i.id
}

// GetLayer returns an io.ReadCloser with uncompressed layer data.
// The layer will always have data, even if that means downloading ALL the image layers from the daemon.
func (i *Image) GetLayer(diffID string) (io.ReadCloser, error) {
layerHash, err := v1.NewHash(diffID)
if err != nil {
return nil, err
}
layer, err := i.LayerByDiffID(layerHash)
if err == nil {
// this avoids downloading ALL the image layers from the daemon
// if the layer is available locally
// (e.g., it was added using AddLayer).
if size, err := layer.Size(); err != nil && size != -1 {
return layer.Uncompressed()
}
}
if err = i.ensureLayers(); err != nil {
return nil, err
}
layer, err = i.LayerByDiffID(layerHash)
if err != nil {
return nil, fmt.Errorf("image %q does not contain layer with diff ID %q", i.Name(), layerHash.String())
}
return layer.Uncompressed()
}

func (i *Image) ensureLayers() error {
var err error
i.downloadOnce.Do(func() {
err = i.Store.DownloadLayersFor(i.lastIdentifier)
})
if err != nil {
return fmt.Errorf("fetching base layers: %w", err)
}
return nil
}

func (i *Image) SetOS(osVal string) error {
if osVal != i.daemonOS {
return errors.New("invalid os: must match the daemon")
}
return i.CNBImageCore.SetOS(osVal)
}

func (i *Image) Rebase(baseTopLayerDiffID string, withNewBase imgutil.Image) error {
if err := i.ensureLayers(); err != nil {
return err
}
return i.CNBImageCore.Rebase(baseTopLayerDiffID, withNewBase)
}

func (i *Image) Save(additionalNames ...string) error {
var err error
i.lastIdentifier, err = i.Store.Save(i, i.Name(), additionalNames...)
return err
}

func (i *Image) SaveAs(name string, additionalNames ...string) error {
var err error
i.lastIdentifier, err = i.Store.Save(i, name, additionalNames...)
return err
}

func (i *Image) SaveFile() (string, error) {
return i.Store.SaveFile(i, i.Name())
}

func (i *Image) Delete() error {
return i.Store.Delete(i.lastIdentifier)
}
Loading

0 comments on commit 6fa1289

Please sign in to comment.